Open61

『プログラミング言語Go』をやっていく会

mohiramohira

まえがき

mohiramohira

2015年の書籍だけど、どうなの? 問題

  • 書籍が出たのは2015年なのでね! Go1.6くらい?
  • 言語仕様というより、ツールチェインが変化している!
  • もちろん、言語仕様も変わっているが、わりと細かいところっぽい
  • 1.18から大きく変化する感じ(ジェネリクスとか)
mohiramohira

周辺のひとたち

  • golang.org/x/textの凄さ!
  • UTF-8つくった人たちだったか! - KenThompson と RobPike が!
  • ブライアン・カーニハンとロブ・パイクは、『プログラミング作法』の著者じゃん
mohiramohira

p.xvii

可変サイズのスタックは最初は十分に小さい

このへん全然わからんちん。

他の言語では、100万個のゴルーチンをつくるみたいなことは、高コストでできないらしい

mohiramohira

Goの「簡潔さ」って何?

原著だと、どんな単語を使っている?

「Goは簡潔」とか「Goはスッキリかける」とかいろんな言い回しがあるような気がするけど、何がどうそうなのやら?

mohiramohira

「安全性のほとんど」

Goはプログラマに安全性のほとんどを与えてくれますし、複雑な型システムの負担なしに、比較的強力な型システムの実行時性能の恩恵を与えてくれます。(p. xvi)

なんつーか、ちょうどいい具合(めんどくさくない。でも、有用)みたいな?

mohiramohira

具象型と抽象型(インタフェース)の関係が暗黙的なところ、おもしろくない?

具象型と抽象型(インタフェース)との関係は暗黙的であるため、具象型の設計者が気づかないうちに、その具象型があるインタフェースを満足しているかもしれません。(p. xviii

普通(というか、他のOOP言語?)だと、明示的だよね。だから、気づくもなにも、気づいている。

「このクラスは、この抽象型を実装していますよー!!!」

って、明示的に書かないといけない!

(Javaとかそういうやつから入ったので、それが普通だと思っていたわけで!)

mohiramohira

第1章 チュートリアル

mohiramohira

go buildしたときに生成されるファイル名

  • go build main.go → ファイル名
  • go buildのみ → ディレクトリ名
$ pwd
dir/helloworld

$ ls
main.go

# ファイル名を指定したbuildはファイル名ままのバイナリ生成
$ go build main.go
$ ls
main    main.go

# ファイル名を指定しないと、ディレクトリ名になる!
$ go build
$ ls
helloworld main       main.go
mohiramohira

build失敗例

$ ls
foo.go bar.go

$ cat foo.go 
package main

import "fmt"

func main() {
        fmt.Println("Hello, 世界")
}

$ cat bar.go 
package main

import "fmt"

func main() {
        fmt.Println("Hello, 世界")
}

$ go build
# github.com/mohira/gopl/ch1/1.1/helloworld
./foo.go:5:6: main redeclared in this block
        previous declaration at ./bar.go:5:6

mohiramohira

だめだよ

import "fmt"

package main

func main() {}
$ go build main.go
main.go:1:1: expected 'package', found 'import'
mohiramohira

i++i--は文であって、式ではない!

こういうことはできないよって話

package main

func main() {
	i := 0

	j := i++
}
$ go run main.go 
# command-line-arguments
./main.go:6:9: syntax error: unexpected ++ at end of statement

Cだとできちゃうから、違いに注意ね!

#include "stdio.h"

int main() {
    int i, j;

    i = 0;

    j = i++;

    printf("i=%d, j=%d\n", i, j); // i=1, j=0
}
$ gcc main.c; ./a.out 
i=1, j=0
mohiramohira

Q. 単文と複文?

あとでな。

p.7らへん

forの文法の中で検証

mohiramohira

Q. 変数宣言時に型を明示的にするときに違いが出るやつ?

  • var x var x int var x float64 の違い
  • varconst
package main

import "fmt"

func main() {
	//var x = 1e+9
	//var x int = 1e+9
	//var x float64 = 1e+9

	const x = 1e+9
	//const x int = 1e+9
	//const x float64 = 1e+9

	var a int
	fmt.Println(a + x)

	var b float64
	fmt.Println(b + x)
}
mohiramohira

mapとかいうデータ型

  • 定数時間で操作
  • キーは、==で比較できる型であればなんでもOK
  • 内部的にはハッシュテーブル
  • JavaのようにEquals()みたいなメソッドを作らなくても、処理系が勝手にハッシュ化してくれるらしい
  • makeすると何が起きるのかは、また4章で。
  • 順序は保持されない
    • 最近のPythonは保持してくれたりするけど、それとは違うね
mohiramohira

bufio.Scannerあたりの挙動理解したい〜〜

気になるやつね。
AtCoderでも使うよね。てきとーに使ってるけど。

mohiramohira

ioutilまわりのAPIがゴリゴリ変わっていく感じっぽい go doc してこうな!

Go1.5から、Go1.16だから、そりゃあいろいろあるよね

go docはWebでみるより楽ちんだ。

$ go doc ioutil.ReadAll
package ioutil // import "io/ioutil"

func ReadAll(r io.Reader) ([]byte, error)
    ReadAll reads from r until an error or EOF and returns the data it read. A
    successful call returns err == nil, not err == EOF. Because ReadAll is
    defined to read from src until EOF, it does not treat an EOF from Read as an
    error to be reported.

    As of Go 1.16, this function simply calls io.ReadAll.

As of Go 1.16, this function simply calls io.ReadAll.

というわけで、io.ReadAllもみていくとこんなん。

$ go doc io.ReadAll
package io // import "io"

func ReadAll(r Reader) ([]byte, error)
    ReadAll reads from r until an error or EOF and returns the data it read. A
    successful call returns err == nil, not err == EOF. Because ReadAll is
    defined to read from src until EOF, it does not treat an EOF from Read as an
    error to be reported.

mohiramohira

Q. 実験してね? コードがほしいぞ! 「文にラベルがつけられる」!?!?」

文にはラベル付けすることができますので、 break と continue はそれらのラベルを参照することがdけいます。それにより、例えば、複数のネストしたループから一度に抜け出したり、最も外側のループの次の繰り返しを開始したりできます。
p.27

ラベル文(Labeld Statements)

https://golang.org/ref/spec#Labeled_statements

A labeled statement may be the target of a goto, break or continue statement.

ほーん?

雰囲気は、こんなんらしい。いまんとこ、見たことがない。

https://golang.org/ref/spec#Continue_statements

RowLoop:
	for y, row := range rows {
		for x, data := range row {
			if data == endOfRow {
				continue RowLoop
			}
			row[x] = data + bias(x, y)
		}
	}

https://imagawa.hatenadiary.jp/entry/2016/12/23/190000

実験した!

  • gofmtでも内側にインデントされない!
  • あんまり使わなさそう
package main

import "fmt"

// 無限ループになるので注意!
func main() {

// gofmtでも内側にインデントされない!
Hello:
	for i := 0; i < 5; i++ {
		fmt.Println(i)

		if i == 1 {
			continue Hello
		}
	}

GoodBye:
	for j := 1; j < 10; j++ {
		if j %2 == 0 {
			goto Hello // あえてgoto使ってみる
		}

		if j == 9 {
			break GoodBye
		}
	}

}
mohiramohira

ラベル文

package main

import "fmt"

func main() {

Name:
	i:=0

	fmt.Println(i)
}
$ go build main.go
./main.go:7:1: label Hello defined and not used
mohiramohira

p.19 1.6が並行処理の威力とGoでの並行処理の書きやすさの例としてGreatなのでは!?

  • fetch()は、成功しても失敗しても、chにデータ送信するようになっている!
  • main()では、<-chでブロックしている
$ go run ch1/1.6/fetchall/main.go https://example.com http://foobarhogefuga.com https://golang.org https://go.pkg.dev https://blog.golang.jp/
Get "http://foobarhogefuga.com": dial tcp: lookup foobarhogefuga.com: no such host
0.42s    9951 https://golang.org
0.52s   86520 https://blog.golang.jp/
0.58s    1256 https://example.com
1.14s  634134 https://go.pkg.dev
1.14s elapsed
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	start := time.Now()
	ch := make(chan string)

	for _, url := range os.Args[1:] {
		go fetch(url, ch)
	}

	for range os.Args[1:] {
		fmt.Println(<-ch)
	}

	fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

func fetch(url string, ch chan string) {
	start := time.Now()

	resp, err := http.Get(url)
	if err != nil {
		ch <- fmt.Sprint(err)
		return
	}

	nbytes, err := io.Copy(io.Discard, resp.Body)
	resp.Body.Close()

	if err != nil {
		ch <- fmt.Sprintf("while reading %s: %v", url, err)
		return
	}

	secs := time.Since(start).Seconds()
	ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
mohiramohira

第2章 プログラム構造

mohiramohira

Q. 予約語(keyword) と 事前宣言(predeclared) の違い

  • 予約語
    • ex: break, func, go, など
    • 識別子としては使えない
  • 事前宣言された名前
    • ex: true, int , makeなど
    • 識別子として使える

ex: keywordは識別子としては使えない

package main

func main() {
	break := 123 //  syntax error: unexpected := at end of statement
}

ex: trueは事前宣言された名前に過ぎないので、識別子として使える

package main

import "fmt"

func main() {
	true := 123
	fmt.Println(true) // 123
}

ex: intも事前宣言された名前に過ぎないので、識別子として使える。で、intを識別子として使うと、型としてのintは使えなくなる

package main

import "fmt"

func main() {
	int := 123
	fmt.Println(int) // 123

	var x int = 99 //  int is not a type
	fmt.Println(x)
}

ちなみに、GoLandだと、この色になるやつ

mohiramohira

Goには未初期化の変数はない

p.32
ゼロ値の仕組みにより、変数はいつでもその方の明確に定義された値をもつことが保証されます。
つまり、Goには未初期化の変数というものはありません。

mohiramohira

eratta: 現状(1.16)の言語仕様には参照型(Referenced Type)という記述はない!

でも、本文では、「参照型」というフレーズが今後も出てくるので注意な!

昔はあったらしい。いつまで?

p.32
インターフェースと参照型(スライス、ポインタ、マップ、チャネル、関数)ならnilです

mohiramohira

未解決:「ヒープにとるか、スタックにとるか」はコンパイラが決める?

このへんしりたいな。
変数の管理ってどうなってるんだ?

package main

import "fmt"

func f() *int {
	v := 1
	return &v
}

var p = f()

// p.35 下段
func main() {
	fmt.Println(p == p)     // true
	fmt.Println(p == f())   // false
	fmt.Println(f() == f()) // false
}
mohiramohira

eratta: named type -> defined type

p.43

type alias が導入されたタイミングで、named type から defined type になったらしい

mohiramohira

索引の秘密 p.121にRobPikeの記述がないとみせかけて...

Rob Pikeさんのアーチェリーネタww

『プログラミング言語Go』の豆知識だ。
索引にあるのにp.121にRobPikeの記述がないと思いきや、あるというww

https://wiki.c2.com/?RobPike

mohiramohira

doc.go

ないものも多い

$ find /usr/local/go/src -name 'doc.go'
/usr/local/go/src/cmd/vet/doc.go
/usr/local/go/src/cmd/asm/doc.go
/usr/local/go/src/cmd/go/internal/doc/doc.go
/usr/local/go/src/cmd/trace/doc.go
/usr/local/go/src/cmd/pprof/doc.go
/usr/local/go/src/cmd/dist/doc.go
/usr/local/go/src/cmd/internal/obj/ppc64/doc.go
/usr/local/go/src/cmd/internal/obj/arm64/doc.go
/usr/local/go/src/cmd/pack/doc.go
/usr/local/go/src/cmd/compile/doc.go
/usr/local/go/src/cmd/link/doc.go
/usr/local/go/src/cmd/cgo/doc.go
/usr/local/go/src/cmd/gofmt/doc.go
/usr/local/go/src/cmd/nm/doc.go
/usr/local/go/src/cmd/fix/doc.go
/usr/local/go/src/cmd/buildid/doc.go
/usr/local/go/src/cmd/vendor/golang.org/x/tools/go/analysis/doc.go
/usr/local/go/src/cmd/vendor/golang.org/x/xerrors/doc.go
/usr/local/go/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/doc.go
/usr/local/go/src/cmd/cover/doc.go
/usr/local/go/src/strconv/doc.go
/usr/local/go/src/net/http/doc.go
/usr/local/go/src/go/doc/doc.go
/usr/local/go/src/go/build/doc.go
/usr/local/go/src/regexp/syntax/doc.go
/usr/local/go/src/fmt/doc.go
/usr/local/go/src/runtime/metrics/doc.go
/usr/local/go/src/runtime/race/doc.go
/usr/local/go/src/internal/race/doc.go
/usr/local/go/src/encoding/gob/doc.go
/usr/local/go/src/html/template/doc.go
/usr/local/go/src/math/big/doc.go
/usr/local/go/src/log/syslog/doc.go
/usr/local/go/src/os/signal/doc.go
/usr/local/go/src/text/template/doc.go
/usr/local/go/src/sync/atomic/doc.go
mohiramohira

p.50 練習問題2-3,2-4,2-5は、最適化されない状態でのベンチマークをとらないと嘘ベンチマークになる

コンパイラ「計算した結果を使ってないから、いらないか。消しとこ。」

みたいなことがある。

すると、

  • ただループを回す速度を測っただけ
  • コンパイル時に計算が完了しているので実際には関数は動いていない

みたいなことになる。

で、どう直すかは、math/bits/bits_test.goの実装を参照するとよい。

グローバルな変数を用意して、それに結果を代入したり、測定したい関数にわたす値をリテラルじゃなくて変数にしたりする感じ。へ〜。

// Exported (global) variable serving as input for some
// of the benchmarks to ensure side-effect free calls
// are not optimized away.
var Input uint64 = DeBruijn64

// Exported (global) variable to store function results
// during benchmarking to ensure side-effect free calls
// are not optimized away.
var Output int
mohiramohira

PopulationsCountのアルゴリズム "Hacker's Delight" という本がよく引用されるらしい

https://www.amazon.co.jp/dp/4434046683

$ go doc math/bits.OnesCount
package bits // import "math/bits"

func OnesCount(x uint) int
    OnesCount returns the number of one bits ("population count") in x.
// --- OnesCount ---

const m0 = 0x5555555555555555 // 01010101 ...
const m1 = 0x3333333333333333 // 00110011 ...
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
const m3 = 0x00ff00ff00ff00ff // etc.
const m4 = 0x0000ffff0000ffff

// OnesCount returns the number of one bits ("population count") in x.
func OnesCount(x uint) int {
	if UintSize == 32 {
		return OnesCount32(uint32(x))
	}
	return OnesCount64(uint64(x))
}

// OnesCount8 returns the number of one bits ("population count") in x.
func OnesCount8(x uint8) int {
	return int(pop8tab[x])
}

// OnesCount16 returns the number of one bits ("population count") in x.
func OnesCount16(x uint16) int {
	return int(pop8tab[x>>8] + pop8tab[x&0xff])
}

// OnesCount32 returns the number of one bits ("population count") in x.
func OnesCount32(x uint32) int {
	return int(pop8tab[x>>24] + pop8tab[x>>16&0xff] + pop8tab[x>>8&0xff] + pop8tab[x&0xff])
}

// OnesCount64 returns the number of one bits ("population count") in x.
func OnesCount64(x uint64) int {
	// Implementation: Parallel summing of adjacent bits.
	// See "Hacker's Delight", Chap. 5: Counting Bits.
	// The following pattern shows the general approach:
	//
	//   x = x>>1&(m0&m) + x&(m0&m)
	//   x = x>>2&(m1&m) + x&(m1&m)
	//   x = x>>4&(m2&m) + x&(m2&m)
	//   x = x>>8&(m3&m) + x&(m3&m)
	//   x = x>>16&(m4&m) + x&(m4&m)
	//   x = x>>32&(m5&m) + x&(m5&m)
	//   return int(x)
	//
	// Masking (& operations) can be left away when there's no
	// danger that a field's sum will carry over into the next
	// field: Since the result cannot be > 64, 8 bits is enough
	// and we can ignore the masks for the shifts by 8 and up.
	// Per "Hacker's Delight", the first line can be simplified
	// more, but it saves at best one instruction, so we leave
	// it alone for clarity.
	const m = 1<<64 - 1
	x = x>>1&(m0&m) + x&(m0&m)
	x = x>>2&(m1&m) + x&(m1&m)
	x = (x>>4 + x) & (m2 & m)
	x += x >> 8
	x += x >> 16
	x += x >> 32
	return int(x) & (1<<7 - 1)
}
mohiramohira

型なしの値

package main

import "fmt"

func main() {
	const x = 1
	var y int
	var z float64
	y = x
	z = x
	fmt.Printf("%T\n", x) // int
	fmt.Printf("%T\n", y) // int
	fmt.Printf("%T\n", z) // float64
}
mohiramohira

2.3.1 省略変数宣言

mohiramohira

宣言(Declarations)と代入(Assignment)は違う! :=は宣言、=は代入

コード例: 宣言してないのでコンパイルエラー

os.Createの行は、既存の変数に代入しているだけであり、宣言はしていない、という雰囲気だと思う。

Goの話ではなく、言葉的なニュアンスとして「宣言」には、「新しいものを定義する」的な感覚があると思う。そういう意味では納得感のある仕様かもしれない。

package main

import "os"

// p.34中段: 宣言 と 代入 は違う!
func main() {
	f, err := os.Open("foo")
	if err != nil {
		panic(err)
	}

	f, err := os.Create("bar") // no new variables on left side of :=

	if err != nil {
		panic(err)
	}
}

もっと短いコード例

package main

import "fmt"

func main() {
	a := 1
	a := 2 // no new variables on left side of :=

	fmt.Println(a)
}
mohiramohira

再宣言(Redeclaration)というのもある

Goの話ではなく、言葉的なニュアンスとして「宣言」には、「新しいものを定義する」的な感覚があると思う。そういう意味では納得感のある仕様かもしれない。

自分でこう書いたけど、同じ名前で別の値を再び宣言するってこともある。

コード例

  • 他の言語で言う「最代入」と同じ雰囲気。
package main

import "fmt"

// ただ多値を返すことを表現するだけの関数
func tuple() (int, int) {
	return 1, 2
}
func main() {
	a, x := tuple()
	a, y := tuple() // a は再宣言、yは新しい識別子なので、コンパイルは通る

	fmt.Println(a, x, y)
}
mohiramohira

ブロックを変えれば、同じ識別子で再度varがいけるが、シャドーイングになっちゃう...

  • これは危険
  • 他のファイルからimportするときに困るやつね
package main

import "fmt"

var a = 1

func main() {
	var a = 2

	fmt.Println(a) // 2 <- 1じゃない!
}
mohiramohira

2.3.2 ポインタ

mohiramohira

ポインタは比較可能

package main

import "fmt"

// p.35 中段
func main() {
	var x, y int
	fmt.Println(&x == &x, &x == &y, &x == nil) // true false false
}
mohiramohira

毎回違うアドレスを返す

package main

import "fmt"

func f() *int {
	v := 1
	return &v
}

var p = f()

// p.35 下段
func main() {
	fmt.Println(p == p)     // true
	fmt.Println(*p == *f()) // true; 値は同じ
	fmt.Println(p == f())   // false; アドレスは違う
	fmt.Println(f() == f()) // false
}
mohiramohira

アドレスを利用して実体をインクリメント

package main

import "fmt"

func incr(p *int) int {
	*p++ // 実体に加算
	return *p
}

func main() {
	v := 1
	fmt.Println(v) // 1

	incr(&v)
	fmt.Println(v) // 2

	fmt.Println(incr(&v)) // 3
}
mohiramohira

flag.Parseを通じてポインタを学ぶ

package main

import (
	"flag"
	"fmt"
	"strings"
)


// それぞれポインタ型を返す
var n *bool = flag.Bool("n", false, "omit trailing newline(改行の省略)")
var sep *string = flag.String("s", " ", "separator")


// p.36
func main() {
	flag.Parse()

    fmt.Printf("%T\n", n) // *bool
    fmt.Printf("%T\n", sep) // *string

    fmt.Println(flag.Args())
	fmt.Print(strings.Join(flag.Args(), *sep))

	if !*n {
		fmt.Println()
	}
}
mohiramohira

2.3.3 new関数

mohiramohira

new

new(T)はT型の**無名変数(unnamed variable)**を作成し、それをTのゼロ値へ初期化し、*T型の値であるそのアドレスを返す。

$ go doc builtin.new
package builtin // import "builtin"

func new(Type) *Type
    The new built-in function allocates memory. The first argument is a type,
    not a value, and the value returned is a pointer to a newly allocated zero
    value of that type.

コード例

package main

import (
	"fmt"
)

func main() {
	p := new(int)
	fmt.Printf("%T\n", p) // *int
	fmt.Println(p)        // 0xc0000140c8
	fmt.Println(*p)       // 0

	*p = 2
	fmt.Println(*p) // 2
}
mohiramohira

new関数はめったに使わない話(p.38)

newは比較的まれにしか使われません

newは構文上の利便性であるだけで基本的な概念ではありません。

newは事前に宣言された関数であり、予約語ではありませんので

mohiramohira

2.3.4 変数の生存期間(lifetime)

  • このへん全然わからん
  • ヒープ領域とスタック領域の話を知りたい
  • ガベージコレクションも重要な話題
  • コンパイラはローカル変数をヒープ上あるいはスタック上のどちらかに生成するかを選択できます。

  • エスケープ(escape)
  • コンパイラが*yをスタック上に割り当てるのは安全です(?)

メモリ管理に参考になりそうな記事

mohiramohira

2.4.2 代入可能性(Assignability)

関数呼び出しは、引数の値を対応するパラメータ変数に暗黙的に代入します。
return文はreturnのオペランドを対応する結果変数へ暗黙的に代入します。

へ〜。代入なのか。

mohiramohira

2.6.2 パッケージ初期化

mohiramohira

init関数は何個でもいける!

package main

import (
	"fmt"
)

func init() {
	fmt.Println("init2")
}

func init() {
	fmt.Println("init1")
}

func init() {
	fmt.Println("init3")
}

func main() {
	fmt.Println("main done")
}
$ go run main.go
init2
init1
init3
main done
mohiramohira

パッケージレベルの変数初期化において依存関係はうまいこと解決している

package main

import "fmt"

// p.49
// c, a, b の順で評価される == 依存関係をうまいこと解決している
var a = valueA()
var b = valueB()
var c = 1

func valueA() int {
	fmt.Println("call valueA()")
	return b + c
}

func valueB() int {
	fmt.Println("call valueB()")
	return c + 1
}

func main() {
	fmt.Println("main start")
	fmt.Println("main done")
}
$ go run main.go
call valueB()
call valueA()
main start
main done
mohiramohira

未検証: コンパイラがパッケージに含まれるファイルを初期化する順序

パッケージが複数の.goファイルから構成されるのであれば、ファイルがコンパイラへ渡された順序で初期化されます。
goツールはコンパイラを呼び出す前に.goをソートします。

mohiramohira

2.7 スコープ

mohiramohira

スコープと生存期間を混同しないように!

宣言のスコープはプログラムテキストの範囲です。すなわちコンパイル時の特性です。

変数の生存期間はその変数がプログラムの他の部分から参照できる実行時の時間の範囲です。すなわち実行時の特性です。

mohiramohira

ループごとに宣言するパターン

  • :=は代入じゃなくて宣言ってことを意識すると理解できる感じがある
package main

import "fmt"

// p.52: 3つのレキシカルブロック
// Goroutineの i:=i も同じっぽい
// 他の言語とくらべると混乱するやつ
func main() {
	x := "hello!"

	for i := 0; i < len(x); i++ {
		fmt.Printf("i=%d\n", i)
		fmt.Printf("\tfor block: x=%v\n", x) // ループごとに x を「宣言」している!
		x := x[i]                            // 2回めのループでのxはどうなるんだ?
		if x != '!' {
			x := x + 'A' - 'a' // 英小文字 を 英大文字 に変換
			fmt.Printf("\t if block: x=%c\n", x)
		}
	}
}
$ go run main.go
i=0
        for block: x=hello!
         if block: x=H
i=1
        for block: x=hello!
         if block: x=E
i=2
        for block: x=hello!
         if block: x=L
i=3
        for block: x=hello!
         if block: x=L
i=4
        for block: x=hello!
         if block: x=O
i=5
        for block: x=hello!
mohiramohira

for文もレキシカルブロックを生み出す

package main

import "fmt"

// p.52 {}がレキシカルブロックってわけじゃない!
// for文の暗黙のレキシカルブロックを作っている
func main() {
	for i := 1; i <= 5; i++ {
		fmt.Printf("outer: i=%d", i)
		i := i*i
		fmt.Printf(" inner: i=%d\n", i)
	}
}
$ go run main.go
outer: i=1 inner: i=1
outer: i=2 inner: i=4
outer: i=3 inner: i=9
outer: i=4 inner: i=16
outer: i=5 inner: i=25
mohiramohira

ifもレキシカルブロックを作る

package main

import "fmt"

func f() int {
	return 0
}

func g(n int) int {
	return 0
}

// p.53
func main() {
	if x := f(); x == 0 {
		fmt.Println(x)
	} else if y := g(x); x == y {
		fmt.Println(x, y)
	} else {
		fmt.Println(x, y)
	}

	fmt.Printf(x, y) // undefined x, y
}
package main

import (
	"fmt"
	"os"
)

func main() {
	if _, err := os.Open("foo"); err != nil {
		panic(err)
	}
	fmt.Println(err) // undefined: err
}

else句もブロックに含まれるよ

package main

import (
	"log"
	"os"
)

func main() {

	if f, err := os.Open("tmp"); err != nil {
		log.Println(err)
	} else {
		// else句もブロックに含まれるのでセーフ
		f.Stat()
	}

}
mohiramohira

レキシカルブロックを理解しておかないと、他のファイルからimportする値を使うときにハマる

package foo

import (
	"log"
	"os"
)

var Cwd string // 実は使われてない!!! GoLandは警告出してくれるね

func init() {
	Cwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("os.Getwd failed: %v\n", err)
	}
	// ここの Cwd は init()内のレキシカルスコープで「宣言」された Cwd
	// なので、パッケージレベルの変数cwdはゼロ値のまま!
	// なので、他のファイルから Cwd を 利用しようとすると...
	log.Printf("Working directory = %s\n", Cwd)
}
package main

import (
	"fmt"
	"github.com/mohira/gopl/ch2/2.7/7/foo"
)

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

	fmt.Println(foo.Cwd=="") // true; つまり foo.Cwdが文字列のゼロ値のまま!
}