talks: state of go 2016 Change-Id: I4c7f1dedbab624f08fef4142765ea094c488311d Reviewed-on: https://go-review.googlesource.com/19121 Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/2016/state-of-go.slide b/2016/state-of-go.slide new file mode 100644 index 0000000..fa48bba --- /dev/null +++ b/2016/state-of-go.slide
@@ -0,0 +1,509 @@ +The State of Go +Where we are in February 2016 + +Francesc Campoy +Gopher at Google +@francesc +campoy@golang.org + +* Time flies + +Go 1.4 is one year old (happy birthday!) + +Go 1.5 is already 6 months old! + +Go 1.6 to be released sometime in February. + +Go 1.6 Candidate Release 1 was released on January 28th + +* Notes + +The slides are available on [[https://talks.golang.org/2016/state-of-go.slide]] + +Most of the code examples won't run except locally and using Go 1.6. + +The playground still runs Go 1.5. + +* Agenda + +Changes since Go 1.5: + +- the language +- the standard library +- the runtime +- the tooling +- the community + +* Changes to the language + +* Changes to the language + +None. + +This is a feature. + +* Changes to the standard library + +* Changes to the standard library + +A couple of new things, in order of excitement. + +- net/http + +- {text,html}/template + +- sort + +Also more speed and fewer bugs. + +* net/http + +If: + +- you're using HTTPS + +- and Go 1.6 + +you're using HTTP/2! + +* HTTP/2 + +At a high level, HTTP/2: + +- is binary, instead of textual +- is fully multiplexed, instead of ordered and blocking +- can therefore use one connection for parallelism +- uses header compression to reduce overhead +- allows servers to “push” responses proactively into client caches + +.link https://http2.golang.org/gophertiles + +* text/template + +Imagine that given a slice of strings: + + []string{"one", "two", "three"} + +We want to write a template that will generate: + + <ul> + <li>one</li> + <li>two</li> + <li>three</li> + </ul> + +What template would you write? + +* text/template: first try + +Naturally, I write this: + +.play state-of-go/template/old.go /`/,/`/ + +But unfortunately it's not exactly what I want! + +* text/template: let's fix it + +We need to be careful with the line breaks. + +.play state-of-go/template/fixed.go /`/,/`/ + +This works now, but ... I don't really like my code! + +* text/template: meet {{- and -}} + +Go 1.6 brings two new delimiters: + +- `{{-` +- `-}}` + +Similar to `{{` and `}}`, but _all_ white space on the `-` side will be trimmed. + +The template: + + {{23 -}} + < + {{- 45}} + +generates: + + 23 < 45 + +* text/template: back to our problem + +We can now have: + +- the result we expected +- without sacrificing the readibility of our templates. + +.play state-of-go/template/new.go /`/,/`/ + +* text/template: the block action + +Go 1.6 brings also a new action named `block`. + +Let's see what it is useful for. + +* Factoring out repetition in templates + +Both `<ul>` share the same structure. + + {{define "presentation"}} + Authors: + <ul> + {{range .Authors}} + <li>{{.}}</li> + {{end}} + </ul> + + Topics: + <ul> + {{range .Topics}} + <li>{{.}}</li> + {{end}} + </ul> + {{end}} + +Templates can be used to avoid repetition. + +* Factoring out repetition with templates (cont.) + +We can define a new template: + +.code state-of-go/template/define.go /define "list"/,/^{{end}}/ + +And use it where needed: + +.code state-of-go/template/define.go /define "presentation"/,/^{{end}}/ + +* Factoring out repetition with templates (cont.) + +We can parse that template and execute it. + +.play state-of-go/template/define.go /func main/,/^}/ + +* Template redefinition + +We made also our template easier to reuse, as we can redefine `list`. + +.play state-of-go/template/redefine.go /func main/,/^}/ + +* Meet the block action + +The `block` action defines and executes a template in place. + +.code state-of-go/template/blocks.go /define/,/`/ + +That template defined by `block` can be: + +- referenced later by the same template, +- redefined with `define`. + +* Why do we need it? + +It is more compact when + +- we're not factoring out repetition, +- but we need to provide an extension point. + +We can make the following template more compact with `block`. + + {{define "content" .}} + <h1>{{.Heading}}<h1> + <p>{{.Content}}</p> + {{end}} + + {{define "page"}} + <title>{{.Title}}</title> + <body> + {{template "content" .}} + </body> + {{end}} + +* Why do we need it? (cont.) + +We can make the following template more compact with `block`. + + {{define "page"}} + <title>{{.Title}}</title> + <body> + {{block "content" .}} + <h1>{{.Heading}}<h1> + <p>{{.Content}}</p> + {{end}} + </body> + {{end}} + +And still easily redefine `content`. + +* sort.Sort is faster + +`Sort` sorts your data by calling `Less`, `Swap`, and `Len`. + +We reduced the number of comparisons and swaps by about 10%. + +Sort `[]int` with Go 1.5 + + BenchmarkSort_1-4 20000000 67.2 ns/op + BenchmarkSort_10-4 10000000 227 ns/op + BenchmarkSort_100-4 500000 3863 ns/op + BenchmarkSort_1000-4 30000 52189 ns/op + +Sort `[]int` with Go 1.6 + + BenchmarkSort_1-4 20000000 64.7 ns/op + BenchmarkSort_10-4 10000000 137 ns/op + BenchmarkSort_100-4 500000 2849 ns/op + BenchmarkSort_1000-4 30000 46949 ns/op + +* sort.Sort is faster - plot + +.image state-of-go/img/bench-sort.png _ 800 + +* Sort order and sort.Stable + +Reminder: sort.Sort is not a _stable_ sort. + +.play state-of-go/sort/unstable.go /byLength/, + +Use `sort.Stable`: + +.play state-of-go/sort/stable.go /func main/, + +* Minor changes + +.image state-of-go/img/minorchanges.png _ 900 +.caption too many to discuss: find them [[https://golang.org/doc/go1.6#minor_library_changes][here]] + +* Let's just discuss one + +`time.Parse` is smarter! + +.play state-of-go/time/time.go /func main/,/^}/ + + +* Changes to the runtime + +* Detection of concurrent map accesses + +Detection of unsafe concurrent access to maps. + +.play state-of-go/runtime/crash.go /const/,/Wait\(\)/ + +Outputs: + + fatal error: concurrent map read and map write + fatal error: concurrent map writes + +* Does it make it slower? + +No! + +Let's benchmark it - with a correct solution. + +.code state-of-go/runtime/good/good.go /func count/,/^}/ + +* Benchmark results + +Go 1.4 - GOMAXPROCS = 4 + + BenchmarkCount_1 1000000 1862 ns/op + BenchmarkCount_10 100000 21214 ns/op + BenchmarkCount_100 1000 1602507 ns/op + BenchmarkCount_1000 10 141712948 ns/op + +Go 1.5 - GOMAXPROCS = 4 + + BenchmarkCount_1-4 2000000 867 ns/op + BenchmarkCount_10-4 200000 6909 ns/op + BenchmarkCount_100-4 1000 1025092 ns/op + BenchmarkCount_1000-4 20 94093719 ns/op + +Go 1.6 - GOMAXPROCS = 4 + + BenchmarkCount_1-4 2000000 750 ns/op + BenchmarkCount_10-4 200000 6582 ns/op + BenchmarkCount_100-4 2000 1113790 ns/op + BenchmarkCount_1000-4 20 87998054 ns/op + +* Benchmark results plot + +.image state-of-go/img/bench4.png _ 800 + +* Benchmark results + +Go 1.4 - GOMAXPROCS = 1 + + BenchmarkCount_1 100000 1370 ns/op + BenchmarkCount_10 20000 8622 ns/op + BenchmarkCount_100 500 362725 ns/op + BenchmarkCount_1000 50 31378803 ns/op + +Go 1.5 - GOMAXPROCS = 1 + + BenchmarkCount_1-4 2000000 776 ns/op + BenchmarkCount_10-4 200000 6288 ns/op + BenchmarkCount_100-4 3000 345037 ns/op + BenchmarkCount_1000-4 50 31751625 ns/op + +Go 1.6 - GOMAXPROCS = 1 + + BenchmarkCount_1-4 2000000 767 ns/op + BenchmarkCount_10-4 200000 6041 ns/op + BenchmarkCount_100-4 5000 329328 ns/op + BenchmarkCount_1000-4 50 30176034 ns/op + +* Benchmark results plot + +.image state-of-go/img/bench1.png _ 800 + +* Garbage Collector in Go 1.5 + +At [[https://www.youtube.com/watch?v=aiv1JOfMjm0][GopherCon 2015 Rick Hudson gave a presentation]] about the Go 1.5 low latency collector + +.image state-of-go/img/gc345.png 500 _ + +* Garbage Collector in Go 1.6 + +At [[https://www.infoq.com/presentations/go-gc-performance][QCon SF in November Rick Hudson]] gave an updated presentation which showed this comparison of Go 1.5 to the upcoming Go 1.6 + +.image state-of-go/img/gc56.png 400 _ + +_Yes,_that_is_gigabytes_on_the_X_axis_ + +* Garbage Collector on tip + +Right now it's even better! + +.image state-of-go/img/gcgotip.png _ 600 + +* People loved it with Go 1.5 + +.image state-of-go/img/twitter1.png _ 550 +.caption [[https://twitter.com/brianhatfield/status/634166123605331968][original tweet]] + +* People love it even more with Go 1.6 + +.image state-of-go/img/twitter2.png _ 600 +.caption [[https://twitter.com/brianhatfield/status/692778741567721473][original tweet]] + +* Oops + +.image state-of-go/img/twitter3.png _ 600 +.caption [[https://twitter.com/bradfitz/status/692787593558118400][original tweet]] and [[https://www.youtube.com/watch?v=CduA0TULnow&t=1m29s][video]] + +* New ports + +Experimental ports to Linux on 64-bit MIPS (linux/mips64 and linux/mips64le). + +Experimental port to Android on 32-bit x86 (android/386). + +On Linux on little-endian 64-bit PowerPC (linux/ppc64le), Go 1.6 now supports cgo with external linking and is roughly feature complete. + +On NaCl, Go 1.5 required SDK version pepper-41. Go 1.6 adds support for later SDK versions. + +* Changes to the tooling + +* The cgo tool + +Go is garbage collected, can C and Go share memory? + +In short: + +- Go can pass a pointer to C +- the referenced memory can't have pointers to Go allocated memory +- C can't keep pointers to the memory after the call returns + +In more detail: + +- the `cgo` [[https://tip.golang.org/cmd/cgo/#hdr-Passing_pointers][docs]]. + +This is checked by the runtime at execution. + +You could disable the checks, but you probably shouldn't. + +* Sharing pointers between Go and C + +.code state-of-go/cgo/main.go + +Outputs: + + panic: runtime error: cgo argument has Go pointer to Go pointer + +* The go tool + +GO15VENDOREXPERIMENT is now enabled by default. + +How does it work? + + /home/user/gocode/ + src/ + server-one/ + main.go (import "github.com/gorilla/mux") + server-two/ + main.go (import "github.com/gorilla/mux") + vendor/ + github.com/ + gorilla/ + mux/ + ... + +`server-one` uses the `mux` package in `$GOPATH/src/github.com/gorilla/mux`. + +`server-two` uses the `mux` package in `vendor`. + +* Vendoring demo + +* go doc + +Go 1.5 added the possibility of searching by name + + go doc math Pi + +Go 1.6 defines priority of packages with import paths with less elements. + +Non vendored packages appear first. + +* go vet + +Go vet warns if the code prints a function instead of its result. + +.play state-of-go/govet/main.go + +`go`vet` output: + + main.go:8: arg foo in Println call is a function value, not a function call + +The warning can be removed using `Printf` and the `%v` or `%p` formats. + +* The community + +* The community + +Code of Conduct announced on November 24th 2015 + +Go meetups: + +- [[http://go-meetups.appspot.com]] + +Women Who Go - 7 chapters already! + +- [[http://www.womenwhogo.org]] + +* Conferences: + +- [[http://gophergala.com/][GopherGala]] Jan 22-24th (judging proposals now) +- [[https://fosdem.org/2016/][FOSDEM]] Right here, right now. Hello! +- [[http://gophercon.ae][GopherCon Dubai]], Feb 23rd +- [[http://www.gophercon.in/][GopherCon India]], Feb 19-20th +- [[https://gophercon.com/][Gophercon Denver]], July 11-13th +- [[http://2016.dotgo.eu/][dotGo]], Oct 10th + +* Go 1.6 release party, February 17th + +Go 1.6 ships soon! + +Go meetups are organising to hold a release party on the 17th of February. + +.image state-of-go/img/party-gopher.png _ 300 +.caption Join the party!!!
diff --git a/2016/state-of-go/cgo/main.go b/2016/state-of-go/cgo/main.go new file mode 100644 index 0000000..1d83eb2 --- /dev/null +++ b/2016/state-of-go/cgo/main.go
@@ -0,0 +1,20 @@ +// +build OMIT + +package main + +/* +int fn(void* arg) { return arg == 0; } +*/ +import "C" +import "unsafe" + +type T struct{ a, b int } +type X struct{ t *T } + +func main() { + t := T{a: 1, b: 2} + C.fn(unsafe.Pointer(&t)) // correct // HL + + x := X{t: &t} + C.fn(unsafe.Pointer(&x)) // incorrect // HL +}
diff --git a/2016/state-of-go/govet/main.go b/2016/state-of-go/govet/main.go new file mode 100644 index 0000000..4f361bd --- /dev/null +++ b/2016/state-of-go/govet/main.go
@@ -0,0 +1,11 @@ +// +build OMIT + +package main + +import "fmt" + +func foo() string { return "bar" } + +func main() { + fmt.Printf("%v", foo) +}
diff --git a/2016/state-of-go/img/bench-sort.png b/2016/state-of-go/img/bench-sort.png new file mode 100644 index 0000000..e42d4a2 --- /dev/null +++ b/2016/state-of-go/img/bench-sort.png Binary files differ
diff --git a/2016/state-of-go/img/bench1.png b/2016/state-of-go/img/bench1.png new file mode 100644 index 0000000..0fee88c --- /dev/null +++ b/2016/state-of-go/img/bench1.png Binary files differ
diff --git a/2016/state-of-go/img/bench4.png b/2016/state-of-go/img/bench4.png new file mode 100644 index 0000000..88cd3d1 --- /dev/null +++ b/2016/state-of-go/img/bench4.png Binary files differ
diff --git a/2016/state-of-go/img/gc345.png b/2016/state-of-go/img/gc345.png new file mode 100644 index 0000000..3180473 --- /dev/null +++ b/2016/state-of-go/img/gc345.png Binary files differ
diff --git a/2016/state-of-go/img/gc56.png b/2016/state-of-go/img/gc56.png new file mode 100644 index 0000000..bc97b2d --- /dev/null +++ b/2016/state-of-go/img/gc56.png Binary files differ
diff --git a/2016/state-of-go/img/gcgotip.png b/2016/state-of-go/img/gcgotip.png new file mode 100644 index 0000000..7088e1c --- /dev/null +++ b/2016/state-of-go/img/gcgotip.png Binary files differ
diff --git a/2016/state-of-go/img/minorchanges.png b/2016/state-of-go/img/minorchanges.png new file mode 100644 index 0000000..3a22ee6 --- /dev/null +++ b/2016/state-of-go/img/minorchanges.png Binary files differ
diff --git a/2016/state-of-go/img/party-gopher.png b/2016/state-of-go/img/party-gopher.png new file mode 100644 index 0000000..d18690f --- /dev/null +++ b/2016/state-of-go/img/party-gopher.png Binary files differ
diff --git a/2016/state-of-go/img/twitter1.png b/2016/state-of-go/img/twitter1.png new file mode 100644 index 0000000..1825bb9 --- /dev/null +++ b/2016/state-of-go/img/twitter1.png Binary files differ
diff --git a/2016/state-of-go/img/twitter2.png b/2016/state-of-go/img/twitter2.png new file mode 100644 index 0000000..81ce982 --- /dev/null +++ b/2016/state-of-go/img/twitter2.png Binary files differ
diff --git a/2016/state-of-go/img/twitter3.png b/2016/state-of-go/img/twitter3.png new file mode 100644 index 0000000..9368027 --- /dev/null +++ b/2016/state-of-go/img/twitter3.png Binary files differ
diff --git a/2016/state-of-go/runtime/crash.go b/2016/state-of-go/runtime/crash.go new file mode 100644 index 0000000..60a07bb --- /dev/null +++ b/2016/state-of-go/runtime/crash.go
@@ -0,0 +1,26 @@ +/// +build OMIT + +package main + +import ( + "fmt" + "sync" +) + +func main() { + const workers = 100 // what if we have 1, 2, 25? + + var wg sync.WaitGroup + wg.Add(workers) + m := map[int]int{} + for i := 1; i <= workers; i++ { + go func(i int) { + for j := 0; j < i; j++ { + m[i]++ // HL + } + wg.Done() + }(i) + } + wg.Wait() + fmt.Println(m) +}
diff --git a/2016/state-of-go/runtime/good/good.go b/2016/state-of-go/runtime/good/good.go new file mode 100644 index 0000000..830cc8e --- /dev/null +++ b/2016/state-of-go/runtime/good/good.go
@@ -0,0 +1,31 @@ +// +build OMIT + +package main + +import ( + "runtime" + "sync" +) + +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + count(10000) // what if we have 1, 2, 25? +} + +func count(n int) { + var wg sync.WaitGroup + wg.Add(n) + m := map[int]int{} + var mu sync.Mutex // HL + for i := 1; i <= n; i++ { + go func(i int) { + for j := 0; j < i; j++ { + mu.Lock() // HL + m[i]++ + mu.Unlock() // HL + } + wg.Done() + }(i) + } + wg.Wait() +}
diff --git a/2016/state-of-go/sort/stable.go b/2016/state-of-go/sort/stable.go new file mode 100644 index 0000000..74041a9 --- /dev/null +++ b/2016/state-of-go/sort/stable.go
@@ -0,0 +1,21 @@ +// +build OMIT + +package main + +import ( + "fmt" + "sort" + "strings" +) + +type byLength []string + +func (b byLength) Len() int { return len(b) } +func (b byLength) Less(i, j int) bool { return len(b[i]) < len(b[j]) } +func (b byLength) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +func main() { + values := []string{"ball", "hell", "one", "joke", "fool", "moon", "two"} + sort.Stable(byLength(values)) // HL + fmt.Println(strings.Join(values, "\n")) +}
diff --git a/2016/state-of-go/sort/unstable.go b/2016/state-of-go/sort/unstable.go new file mode 100644 index 0000000..3923a2c --- /dev/null +++ b/2016/state-of-go/sort/unstable.go
@@ -0,0 +1,21 @@ +// +build OMIT + +package main + +import ( + "fmt" + "sort" + "strings" +) + +type byLength []string + +func (b byLength) Len() int { return len(b) } +func (b byLength) Less(i, j int) bool { return len(b[i]) < len(b[j]) } +func (b byLength) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +func main() { + values := []string{"ball", "hell", "one", "joke", "fool", "moon", "two"} + sort.Sort(byLength(values)) + fmt.Println(strings.Join(values, "\n")) +}
diff --git a/2016/state-of-go/template/blocks.go b/2016/state-of-go/template/blocks.go new file mode 100644 index 0000000..c549ee6 --- /dev/null +++ b/2016/state-of-go/template/blocks.go
@@ -0,0 +1,48 @@ +// +build OMIT + +package main + +import ( + "html/template" + "log" + "os" + "strings" +) + +const tmplText = ` +{{define "presentation"}} + Authors: + {{block "list" .Authors}} // HL + <ul> + {{- range .}} + <li>{{.}}</li> + {{- end}} + </ul> + {{end}} // HL + + Topics: + {{template "list" .Topics}} // HL +{{end}} +` // OMIT + +type Presentation struct { + Authors []string + Topics []string +} + +func main() { + p := Presentation{ + Authors: []string{"one", "two", "three"}, + Topics: []string{"go", "templates"}, + } + + tmpl := template.Must(template.New("presentation").Parse(tmplText)) + + tmpl = tmpl.Funcs(template.FuncMap{"join": strings.Join}) // HL + tmpl = template.Must(tmpl.Parse(`{{define "list"}} {{join . " | "}} {{ end}}`)) // HL + + err := tmpl.Execute(os.Stdout, p) + if err != nil { + log.Fatal(err) + } +}
diff --git a/2016/state-of-go/template/define.go b/2016/state-of-go/template/define.go new file mode 100644 index 0000000..046f493 --- /dev/null +++ b/2016/state-of-go/template/define.go
@@ -0,0 +1,46 @@ +// +build OMIT + +package main + +import ( + "html/template" + "log" + "os" +) + +const tmplText = ` +{{define "list"}} + <ul> + {{range .}} + <li>{{.}}</li> + {{end}} + </ul> +{{end}} + +{{define "presentation"}} + Authors: + {{template "list" .Authors}} + + Topics: + {{template "list" .Topics}} +{{end}} +` + +type Presentation struct { + Authors []string + Topics []string +} + +func main() { + p := Presentation{ + Authors: []string{"one", "two", "three"}, + Topics: []string{"go", "templates"}, + } + + tmpl := template.Must(template.New("presentation").Parse(tmplText)) // HL + + err := tmpl.Execute(os.Stdout, p) + if err != nil { + log.Fatal(err) + } +}
diff --git a/2016/state-of-go/template/fixed.go b/2016/state-of-go/template/fixed.go new file mode 100644 index 0000000..ddda356 --- /dev/null +++ b/2016/state-of-go/template/fixed.go
@@ -0,0 +1,22 @@ +// +build OMIT + +package main + +import ( + "html/template" + "log" + "os" +) + +var tmpl = template.Must(template.New("tmpl").Parse(` +<ul> +{{range .}}<li>{{.}}</li> +{{end}}</ul> +`)) + +func main() { + err := tmpl.Execute(os.Stdout, []string{"one", "two", "three"}) + if err != nil { + log.Fatal(err) + } +}
diff --git a/2016/state-of-go/template/new.go b/2016/state-of-go/template/new.go new file mode 100644 index 0000000..fc5d545 --- /dev/null +++ b/2016/state-of-go/template/new.go
@@ -0,0 +1,24 @@ +// +build OMIT + +package main + +import ( + "html/template" + "log" + "os" +) + +var tmpl = template.Must(template.New("tmpl").Parse(` +<ul> +{{range . -}} + <li>{{.}}</li> +{{end -}} +</ul> +`)) + +func main() { + err := tmpl.Execute(os.Stdout, []string{"one", "two", "three"}) + if err != nil { + log.Fatal(err) + } +}
diff --git a/2016/state-of-go/template/old.go b/2016/state-of-go/template/old.go new file mode 100644 index 0000000..f5156c4 --- /dev/null +++ b/2016/state-of-go/template/old.go
@@ -0,0 +1,24 @@ +// +build OMIT + +package main + +import ( + "html/template" + "log" + "os" +) + +var tmpl = template.Must(template.New("tmpl").Parse(` +<ul> +{{range .}} + <li>{{.}}</li> +{{end}} +</ul> +`)) + +func main() { + err := tmpl.Execute(os.Stdout, []string{"one", "two", "three"}) + if err != nil { + log.Fatal(err) + } +}
diff --git a/2016/state-of-go/template/redefine.go b/2016/state-of-go/template/redefine.go new file mode 100644 index 0000000..264768c --- /dev/null +++ b/2016/state-of-go/template/redefine.go
@@ -0,0 +1,50 @@ +// +build OMIT + +package main + +import ( + "html/template" + "log" + "os" + "strings" +) + +const tmplText = ` +{{define "list"}} + <ul> + {{range .}} + <li>{{.}}</li> + {{end}} + </ul> +{{end}} + +{{define "presentation"}} + Authors: + {{template "list" .Authors}} + + Topics: + {{template "list" .Topics}} +{{end}} +` + +type Presentation struct { + Authors []string + Topics []string +} + +func main() { + p := Presentation{ + Authors: []string{"one", "two", "three"}, + Topics: []string{"go", "templates"}, + } + + tmpl := template.Must(template.New("presentation").Parse(tmplText)) + + tmpl = tmpl.Funcs(template.FuncMap{"join": strings.Join}) // HL + tmpl = template.Must(tmpl.Parse(`{{define "list"}} {{join . " | "}} {{ end}}`)) // HL + + err := tmpl.Execute(os.Stdout, p) + if err != nil { + log.Fatal(err) + } +}
diff --git a/2016/state-of-go/time/time.go b/2016/state-of-go/time/time.go new file mode 100644 index 0000000..a94d218 --- /dev/null +++ b/2016/state-of-go/time/time.go
@@ -0,0 +1,18 @@ +// +build OMIT + +package main + +import ( + "fmt" + "time" +) + +func main() { + days := []string{"2015 Feb 29", "2016 Feb 29", "2017 Feb 29"} + + fmt.Println("Are these days valid?") + for _, day := range days { + _, err := time.Parse("2006 Jan 2", day) + fmt.Printf("%v -> %v\n", day, err == nil) + } +}