Go for C++ developers
Francesc Campoy
Developer, Advocate, and Gopher at Google
Francesc Campoy
Developer, Advocate, and Gopher at Google
Agenda:
2011 Joined Google as a Software Engineer
2012 Joined the Go team as a Developer Programs Engineer
2014 to today Developer Advocate for the Google Cloud Platform
3Go is
Go was created for:
Google:
Others:
int, uint, int8, uint8, ... bool, string float32, float64 complex64, complex128
struct {
Name string
Age int
}[]int, [3]string, []struct{ Name string }map[string]int
*int, *Person
func(int, int) int
chan bool
interface {
Start()
Stop()
}type [name] [specification]
Person is a struct type.
type Person struct {
name string
age int
}
Celsius is a float64 type.
type Celsius float64
func [name] ([params]) [return value] func [name] ([params]) ([return values])
A sum function:
func sum(a int, b int) int {
return a + b
}A function with multiple return values:
func divMod(a, b int) (int, int) {
return a / b, a % b
}Made clearer by naming the return values:
func divMod(den, div int) (quo, rem int) {
return den / div, den % div
}func ([receiver]) [name] ([params]) ([return values])
A method on a struct:
func (p Person) IsMinor() bool {
return p.age < 18
}
But also a method on a float64:
func (c Celsius) Freezing() bool {
return c <= 0
}Constraint: Methods can be defined only on types declared in the same package.
// This won't compile
func (s string) Length() int { return len(s) }Normal declaration:
var text string = "hello"
You can omit types:
var text = "hello"
And inside of functions:
text := "hello"
Other types
a := 0 // int
b := true // boolean
f := 1.0 // float64
p := Person{"Francesc", "Campoy"} // PersonGiven types:
type Celsius float64 type Fahrenheit float64
And the variables:
var freezing Fahrenheit = 32 var boiling Celsius = 100
This code won't compile:
sauna := (freezing + boiling) / 2
There's no implicit numeric conversion in Go.
16Go has pointers:
var p *int p = new(int)
But no pointer arithmetics:
var p *int = &a[0] var q = p+2 // invalid
There's new but there's no delete.
Memory is garbage collected after it's no longer accessible.
18The compiler decides where to allocate based on escape analysis.
Using new doesn't imply using the heap:
stack.go:
func get() int {
n := new(int)
return *n
}
And not all values in the heap are created with new:
heap.go:
func get() *int {
n := 4
return &n
}You can not decide where a value is allocated.
But you can see what kind of allocation is used:
$ go tool 6g -m stack.go stack.go:3: can inline get stack.go:4: get new(int) does not escape
Compare to:
$ go tool 6g -m heap.go heap.go:3: can inline get heap.go:4: moved to heap: n heap.go:5: &n escapes to heap
Resource Acquisition Is Initialization
Provides:
An example:
void write_to_file (const std::string & message) {
// mutex to protect file access
static std::mutex mutex;
// lock mutex before accessing file
// at the end of the scope unlock mutex
std::lock_guard<std::mutex> lock(mutex);
// mutual exclusion access section
...
}
The defer statement:
var m sync.Mutex
func writeToFile(msg string) error {
m.Lock()
defer m.Unlock()
// mutual exclusion access section
}Go is a garbage collected language
But it's easy to limit heap allocations
// +build ignore,OMIT
package main
import (
"fmt"
"unsafe"
)
type Date struct { Day int Month int Year int } func main() { fmt.Printf("size of %T: %v\n", 0, unsafe.Sizeof(0)) fmt.Printf("size of %T: %v\n", Date{}, unsafe.Sizeof(Date{})) fmt.Printf("size of %T: %v\n", [100]Date{}, unsafe.Sizeof([100]Date{})) }
Trusted in production.
Brad Fitzpatrick's talk on migrating dl.google.com from C++ to Go:
Current state and road plan:
24Example:
type Engine struct{}
func (e Engine) Start() { ... }
func (e Engine) Stop() { ... }
We want Car to be able to Start and Stop too.
More detail in my talk Go for Javaneros
26Composition + Proxy of selectors
// +build ignore,OMIT
package main
import "fmt"
type Engine struct{} func (e Engine) Start() { fmt.Println("Engine started") } func (e Engine) Stop() { fmt.Println("Engine stopped") } type Car struct { Engine // Notice the lack of name } func main() { var c Car c.Start() c.Stop() }
What if two embedded fields have the same type?
// +build ignore,OMIT
package main
import "fmt"
type Engine struct{} func (e Engine) Start() { fmt.Println("Engine started") } func (e Engine) Stop() { fmt.Println("Engine stopped") } type Radio struct{} func (r Radio) Start() { fmt.Println("Radio started") } func (r Radio) Stop() { fmt.Println("Radio stopped") } type Car struct { Engine Radio } func main() { var c Car c.Radio.Start() c.Engine.Start() }
It looks like inheritance but it is not inheritance.
It is composition.
Used to share implementations between different types.
What if want to share behavior instead?
29An interface is a set of methods.
In Java (C++ doesn't have interfaces)
interface Switch {
void open();
void close();
}In Go:
type OpenCloser interface {
Open()
Close()
}Java interfaces are satisfied explicitly.
C++ abstract classes need to be extended explicitly
Go interfaces are satisfied implicitly.
If a type defines all the methods of an interface, the type satisfies that interface.
Benefits:
Better than duck typing. Verified at compile time.
Package parse provides a parser of strings into functions.
func Parse(text string) (*Func, error) { ... }
Func is a struct type, with an Eval method.
type Func struct { ... }
func (p *Func) Eval(x float64) float64 { ... }Package draw generates images given a function.
func Draw(f *parser.Func) image.Image {
for x := start; x < end; x += inc {
y := f.Eval(x)
...
}
}
draw depends on parser, which makes testing hard.
Let's use an interface instead
type Evaluable interface {
Eval(float64) float64
}
func Draw(f Evaluable) image.Image image.Image {
for x := start; x < end; x += inc {
y := f.Eval(x)
...
}
}Embedding an interface:
Given:
type Person struct {
First string
Last string
Age int
}
Employee exposes the Age of Person
type Employee struct {
Person
}
e := Employee{Person{"John", "Doe", 49}}But we could hide it by choosing an interface:
type Employee struct {
Namer
}
type Namer interface {
Name() string
}
And we need to make Person satisfy Namer
func (e Person) Name() string { return e.First + e.Last }And the rest of the code still works:
e := Employee{Person{"John", "Doe", 49}}Given this function:
func CheckPassword(c net.Conn) error { // read a password from the connection buf := make([]byte, 256) n, err := c.Read(buf) if err != nil { return fmt.Errorf("read: %v", err) } // check it's correct got := string(buf[:n]) if got != "password" { return fmt.Errorf("wrong password") } return nil }
How would you test it?
42
net.Conn is an interface defined in the net package of the standard library.
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
We need a fake net.Conn!
We could define a new type that satisfies net.Conn
type fakeConn struct {}
func (c fakeConn) Read(b []byte) (n int, err error) {...}
func (c fakeConn) Write(b []byte) (n int, err error) {...}
func (c fakeConn) Close() error {...}
...But, is there a better way?
44type fakeConn struct { net.Conn r io.Reader } func (c fakeConn) Read(b []byte) (int, error) { return c.r.Read(b) }
And our test can look like:
// +build ignore,OMIT
package main
import (
"fmt"
"io"
"log"
"net"
"strings"
)
func CheckPassword(c net.Conn) error {
// read a password from the connection
buf := make([]byte, 256)
n, err := c.Read(buf)
if err != nil {
return fmt.Errorf("read: %v", err)
}
// check it's correct
got := string(buf[:n])
if got != "password" {
return fmt.Errorf("wrong password")
}
return nil
}
type fakeConn struct {
net.Conn
r io.Reader
}
func (c fakeConn) Read(b []byte) (int, error) {
return c.r.Read(b)
}
// end_fake OMIT
func main() { c := fakeConn{ r: strings.NewReader("foo"), } err := CheckPassword(c) if err == nil { log.Println("expected error using wrong password") } else { log.Println("OK") } }
It is part of the language, not a library.
Based on three concepts:
func sleepAndTalk(t time.Duration, msg string) { time.Sleep(t) fmt.Printf("%v ", msg) }
We want a message per second.
// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
func sleepAndTalk(t time.Duration, msg string) {
time.Sleep(t)
fmt.Printf("%v ", msg)
}
func main() { sleepAndTalk(0*time.Second, "Hello") sleepAndTalk(1*time.Second, "Gophers!") sleepAndTalk(2*time.Second, "What's") sleepAndTalk(3*time.Second, "up?") }
What if we started all the sleepAndTalk concurrently?
Just add go!
// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
func sleepAndTalk(t time.Duration, msg string) {
time.Sleep(t)
fmt.Printf("%v ", msg)
}
func main() { go sleepAndTalk(0*time.Second, "Hello") go sleepAndTalk(1*time.Second, "Gophers!") go sleepAndTalk(2*time.Second, "What's") go sleepAndTalk(3*time.Second, "up?") }
That was fast ...
When the main goroutine ends, the program ends.
// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
func sleepAndTalk(t time.Duration, msg string) {
time.Sleep(t)
fmt.Printf("%v ", msg)
}
func main() { go sleepAndTalk(0*time.Second, "Hello") go sleepAndTalk(1*time.Second, "Gophers!") go sleepAndTalk(2*time.Second, "What's") go sleepAndTalk(3*time.Second, "up?") time.Sleep(4 * time.Second) }
But synchronizing with Sleep is a bad idea.
sleepAndTalk sends the string into the channel instead of printing it.
func sleepAndTalk(secs time.Duration, msg string, c chan string) { time.Sleep(secs * time.Second) c <- msg }
We create the channel and pass it to sleepAndTalk, then wait for the values to be sent.
// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
func sleepAndTalk(secs time.Duration, msg string, c chan string) {
time.Sleep(secs * time.Second)
c <- msg
}
func main() { c := make(chan string) go sleepAndTalk(0, "Hello", c) go sleepAndTalk(1, "Gophers!", c) go sleepAndTalk(2, "What's", c) go sleepAndTalk(3, "up?", c) for i := 0; i < 4; i++ { fmt.Printf("%v ", <-c) } }
A production ready web server.
package main import ( "fmt" "log" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello") } func main() { http.HandleFunc("/", handler) err := http.ListenAndServe("localhost:1234", nil) if err != nil { log.Fatal(err) } }
Why is this wrong?
// +build ignore,OMIT
package main
import (
"fmt"
"log"
"net/http"
)
var nextID int func handler(w http.ResponseWriter, q *http.Request) { fmt.Fprintf(w, "<h1>You got %v<h1>", nextID) nextID++ } func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe("localhost:8080", nil)) }
We receive the next id from a channel.
var nextID = make(chan int) func handler(w http.ResponseWriter, q *http.Request) { fmt.Fprintf(w, "<h1>You got %v<h1>", <-nextID) }
We need to send ids into the channel.
func counter() { for i := 0; ; i++ { nextID <- i } }
And we need to do both at the same time.
// +build ignore,OMIT
package main
import (
"fmt"
"log"
"net/http"
)
var nextID = make(chan int)
func handler(w http.ResponseWriter, q *http.Request) {
fmt.Fprintf(w, "<h1>You got %v<h1>", <-nextID)
}
func counter() {
for i := 0; ; i++ {
nextID <- i
}
}
func main() { http.HandleFunc("/", handler) go counter() log.Fatal(http.ListenAndServe("localhost:8080", nil)) }
select allows us to choose among multiple channel operations.
// +build ignore,OMIT
package main
import (
"fmt"
"net/http"
)
var battle = make(chan string) func handler(w http.ResponseWriter, q *http.Request) { select { case battle <- q.FormValue("usr"): fmt.Fprintf(w, "You won!") case won := <-battle: fmt.Fprintf(w, "You lost, %v is better than you", won) } }
func main() {
http.HandleFunc("/fight", handler)
http.ListenAndServe("localhost:8080", nil)
}
Go - localhost:8080/fight?usr=go
C++ - localhost:8080/fight?usr=cpp
Ok, I'm just bragging here
57// +build ignore,OMIT
package main
import (
"fmt"
"time"
)
func f(left, right chan int) { left <- 1 + <-right } func main() { start := time.Now() const n = 1000 leftmost := make(chan int) right := leftmost left := leftmost for i := 0; i < n; i++ { right = make(chan int) go f(left, right) left = right } go func(c chan int) { c <- 0 }(right) fmt.Println(<-leftmost, time.Since(start)) }
And there's lots to learn!
Learn Go on your browser with go.dev/tour
Find more about Go on go.dev
Join the community at golang-nuts
Link to the slides /talks/2015/go4cpp.slide
61