Logo address

初めての Golang

2012/06/05
2022/01/18 更新

C についての基礎的な知識があることを想定。C と違う点を重点的に解説する。

Installation

2022/01/25

Installation from binary package

Golang の公式サイトには Linux, Mac, Windows 用の、すぐに使える状態の Go のパッケージが置かれている:
https://go.dev/dl/
から OS に合わせたパッケージをダウンロードする。

例を PC Linux にとれば go1.17.6.linux-amd64.tar.gz をダウンロードすればよい。これをどこかに置く1。僕の場合には(今後、何回もバージョンアップがあることを想定して)置き場所を

GOLANG=$HOME/src/golang
に決めている。ダウンロードしたファイルを解凍するとディレクトリ $GOLANG/go が生成され、ここにパッケージが展開されている。Go のソース・ファイルをコンパイルするのに必要な実行ファイルが $GOLANG/go/bin に置かれている。

環境変数

GOROOT=$GOLANG/go; export GOROOT
PATH=$PATH:$GOROOT/bin/go; export PATH
を設定すれば、それだけで Go が使えるようになる。


注1: 公式サイトの推奨インストール先は /usr/local/go である

Installation from source

既に Go が $GOLANG にインストールされていると仮定する。従って $GOLANG/go が既に存在する。その下で
go1.17.6.src.tar.gz
をインストールするプロセスを解説する。ここでは Linux あるいは Mac を想定する。
cd $GOLANG
mv go bootstrap
tar -xf go1.17.6.src.tar	# produces go
GOROOT_BOOTSTRAP=$GOLANG/bootstrap
cd go/src
all.bash
すると次のメッセージを得る:
all.bash
Building Go cmd/dist using /home/arisawa/src/golang/bootstrap. (go1.17.6 linux/amd64)
Building Go toolchain1 using /home/arisawa/src/golang/bootstrap.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for linux/amd64.

##### Testing packages.
ok  	archive/tar	0.060s
ok  	archive/zip	0.142s
...
...
ALL TESTS PASSED
---
Installed Go for linux/amd64 in /home/arisawa/src/golang/go
Installed commands in /home/arisawa/src/golang/go/bin
これで終了。
メッセージの詳細

問題点

この方法だと、Go をアップデートしている間は Go を使えないことになる。Go を実験的に使っている場合には問題ないが、Go がサーバーのサービスに使われている場合には問題ではないか?

Plan9 へのインストール

マイナーな OS の場合には binary package が Golang のページには載っていない。例えば Plan9 の場合がそうである。
Plan9 へのインストールは http://p9.nyx.link/golang/ で解説されている。この解説が参考になるかも知れない。

プログラムの構造

完全な文法は
The Go Programming Language Specification
https://go.dev/ref/spec
に書かれている。しかし、これを初心者が読むのはしんどいだろう。

package main

func fun1(n int){
	for k:=0; k < n; k++ {
		print(k," ")
	}
	println()
}

func main(){
	println("こんにちは")
	var x,y int
	x = 20
	y = 3
	println(x+y,x*y,x/y,x%y)
	fun1(10)
}
譜1. a.go

the result

basg$ go run a.go
こんにちは
23 60 6 2
0 1 2 3 4 5 6 7 8 9
-bash$

改行

プログラム中の改行コードは全て ";" に置き換えられて解釈される。";" は C と同様に命令の区切り子である。
従って
func main()
{
と書くとシンタックスエラーになる。なぜなら、
func main();{
と解釈されるから。

ブロック

C と同様にブロックの範囲は "{ }"
で示す。
ブロックの中の命令が1つの場合も、"{ }" を省けない。

ファイル名

任意

環境変数:
GOROOT: 配布パッケージの場所を知らせる。配布パッケージはどこに置いてもよい。
GOPATH: ユーザー作成(サードパーティを含む) の Go 関係のファイルの場所を知らせる。
Go のプログラム自体はどこに置いても可。しかし $GOPATH/src の下に置くのが管理上望ましい。

go env GOPATH
で現在の GOPATH が判る。GOPATH が環境変数として定義されていない場合には
$HOME/go
GOPATH であるとされる。

package main

package 宣言は必須
関数 main を含む場合には main を指定

func main

引数無し,返り値なし
コマンドインターフェースとしての引数は、他の方法で得る。詳しくは io.Args を見よ。
終了コードは、正常に終了すれば 0
注: C のように、return 0 あるいは exit(0) で終わる必要はない。

変数宣言

Go では暗黙の型宣言は許されない。そして宣言とともに初期化が行われる。
var x,y int
「変数 x,y は int である」と読む。
初期化が行われている。(初期値は 0)

変数の型の決定は、明示的な宣言によるか、または代入による。
以下の3つのスタイルがある:
(1) var x,y,z int // x=0, y=0, z=0 で初期化
(2) var x,y,z = 2,3,5 // x=2, y=3, z=5 で初期化
(3) x,y,z := 2,3,5 // x=2, y=3, z=5 で初期化

注1: (3) は代入文と言うより、宣言文である。
(2) と (3) の実質的な違いはどこにあるか? → 注3 および 注4 を見よ
そもそも (2) は言語設計として必要なのか? → 注4 を見よ

こんな書き方も許される:

var(
	a = 2
	b = "foo"
	c = true
)
関数の中でも、外でも OK

注2: (1) のタイプのように、明示的な型宣言はプログラミングの大きな負担である。僕が Java を使わない最大の理由がこの点にある。インタープリタ言語の多くは、型宣言を要しないので気楽にプログラムができる。他方では可読性を害し、バグの原因になり、バグ取りを困難にする。Go はこの問題を解決しようとしている。

注3: (1) と (2) のタイプは関数の外で使えるが、(3) のタイプは関数の外では使えない。

注4: (3) のタイプは、その変数が利用されていないとコンパイラに文句を言われ、コンパイルできない。他方、(1) や (2) のタイプは、関数の外で宣言された場合には、その変数が利用されていなくてもコンパイラは文句を言わない。(この特徴は package の中で宣言あるいは初期化された多数の変数を利用できるようにするために必要である。)

演算

整数に対する演算は言語Cと同じ。
文字列に対して
"abc"+"de"
のような和の演算が許されるようになったのは非常にありがたい。

混合演算

混合演算に対するチェックは厳しい。例えば
var a byte; var b int
と宣言しておいて
a + b
は型チェックで跳ねられる。(このケースでは許しても良いような気はする)
int(a)+b
と書く必要がある。定数との演算は、結果良ければ良し。

println

println は改行を伴う。以下に標準出力に吐き出す類似の関数を挙げる。
print
println
fmt.Print
fmt.Println
fmt.Printf
print()println() は原始的な出力関数で、正式のプログラムコードでは使わない方が良い。デバッグ用に限定して使える。原始的なるが故に、Go の動作を理解するのに良いかも知れない。

print() は改行を伴わない。また、データとデータの区切りに空白を付加しない。

print()println() は、ポインタ変数に対してはアドレスを出力する。他方参照しているデータを書き出すには fmt package の関数を使う。
fmt を使う時には import 宣言が必要

package main
import (
	"fmt"
)
のように宣言しておいて
fmt.Println("Hello")
のようにして使う。"fmt." が面倒なら
package main
import (
	. "fmt"
)
とする。すると
Println("Hello")
のように "fmt." を省ける。なぜ原始的な出力関数を大文字で始めなかったかが理解できるだろう。なお Go の名前規則の中に、小文字で始める名前は package の中で private、大文字で始める名前は public というのがある。public な名前は、他の package から名前が参照される。

ここに挙げた出力関数はバッファリングしないので遅い。デバッグ用。

バッファリングが必要なら、"bufio" を使う。

文字列 (string)

Unicode (UTF8)対応になっている。(ソースファイルは UTF-8 でなくてはならない)
Shift-JIS 環境や EUC 環境では、文字コードを UTF-8 に変換して、Go に渡す必要がある。
文字列定数は、二重引用符(double quote)や逆引用符(back quote)を使って
"a string in double quote"
`a string in back quote`
のように表す。
前者は \ がエスケープ文字。行を跨げない。C の文字列と良く似ている。
後者は \ に特別の意味は無いので、正規表現に都合が良い。行を跨ぐ事が許される。

文字 (rune)

この例には現れないが、
文字 (Unicode の意味で1文字)
'a'
'あ'
のように一重引用符(single quote) ' ' で囲む。この辺は C と同じであるが、許される文字は Unicode に拡張されている。

注: これは Unicode の 1文字でなくてはならない。プログラムコードが UTF-8 になっていないと、1文字を ' ' で括ったつもりでも、1文字と認識しないのでエラーになるだろう。

runeuint32 の別名
byteuint8 の別名

繰り返し

C 風の書き方がサポートされている。

for k:=0; k < n; k++ {
	print(k," ")
}

しかし次の書き方は NG で要注意。

package main
import(
	. "fmt"
)
func f(n,m int) bool{
	if n >= m {
		return false
	}
	return true
}

func main(){
	ok := true
	for k:=0;ok;k++ {
		//ok = f(k,3) // OK
		ok := f(k,3) // NG
		Println(k,ok)
	}
}

うまく行かない原因は、for の条件部の okfor の本体ブロックの中での ok とは異なるからである。ここは ":=" ではなく "=" としなくてはならない。(コンパイラがチェックしてくれるべきだと思う)


注1:
C のスタイルであれば
for(k=0; k < n; k++) {
  ......
}
である。

注2:
while ~ {....}do {....} until ~ は Go には無い。for だけでやっていく。

詳しくは後に。


Printf

主な format

Sscanf と Sprintf

数値を表す文字列を読み取る。逆に数値を文字列に変換する。
package main
// Ssanf
// go doc fmt.Sscanf
import (
	. "fmt"
)
func main() {
	var x,y int
	// func Sscanf(str string, format string, a ...interface{}) (n int, err error)
	input := "123 45"
	Println(input)
	n,err := Sscanf(input, "%d %x",&x, &y)
	Println(err,n,x,y)
	s := Sprintf("%d %x", x, y)
	Println(s)
}

output:

123 45
<nil> 2 123 69
123 45

文字列(string)、byte 列、文字(rune)

文字に関する、いくつかの基本型: string, byte, rune

例1

package main

// quote

import "fmt"

func main(){
  var s = "こんにちは"
  var b = []byte(s)
  var t = string(b)
  var u = 'あ' // this type is rune
  println(s)		// こんにちは
  println(b)		// [15/15]0xf840028220
  fmt.Println(b) 	// [227 129 147 227 130 147 227 129 171 227 129 161 227 129 175]
  println(t) 		// こんにちは
  fmt.Printf("%x\n",u)	// 3042
 }

/*
	output is shown at the right side of "//"
	3042 is 0x3042, unicode of 「あ」
*/

コメント

コメントの書き方は C と同じ。次の2つが使える。
//  これより右がコメント (行末まで)
/* この中がコメント */

import

モジュール(package)の取り込み。使い方は Python の import と良く似ているが、以下の違いがある。
  1. "fmt" は package 名と言うより、package パッケージが置かれている path を表している。
    " " で囲っているのはそのためであろう。
  2. ここでは import の簡単な例しか挙げていないが、Python の import で行える全ての事がサポートされている。(ただし、書き方が異なる。)

型変換

C との大きな違いは、文字列型の扱いである。C では文字列は特殊な(つまり最後の要素が0で終わる)8bitsの符号付き整数であったが、Go では、文字列は独自の地位を与えられている。文字列は byte 型データ(8bitsの符号無し整数)の配列のスライスである。他方
var s = "こんにちは"
var b = []byte(s)
に現れる
[]byte
は、同様にbyte型データの配列の範囲を示すスライスである。
var b = []byte(s)
で、s を基に slice 型データが作られる。「基に」と言ったのは、b が指し示しているのは s そのものではないからである。(s のコピーである)

この例題で示されているように、string と byte 配列 ( []byte ) は相互に変換できる。
string はもっぱら UTF-8 でエンコードされた文字列のための変数型であるが、任意の byte 列を string に変換できるようである。次の例で、string と []byte ととの違いを示す。

	var a string
	var b []byte
	a = "Hello"
	b = []byte(a)
	fmt.Println(a, a[2:5],b[2:5]) 	// Hello ell [108 108 111]

文字列の長さ

文字列 s の長さは関数 len(s) で分かる。
len("こんにちは") は 5 ではなく、15 を返す。15 は "こんにちは" を UTF-8 でエンコードしたときの byte 数である。

補注: string と言いながら string らしからぬこの規則は変なのである。

例2

package main
import "fmt"
func main(){
  var s = "こんにちは"
  var b = []byte(s)
  var t = string(b)
  println(len(s),s)	// こんにちは
  println(len(b),b)	// [15/15]0xf840028220
  fmt.Println(b) // [227 129 147 227 130 147 227 129 171 227 129 161 227 129 175]
  println(len(t),t) // こんにちは
  b[0] = 0
  b[1] = 0
  b[2] = 0
  t = string(b)
  println(len(t),t)
  println(len(s),s)
}
この実行結果は
15 こんにちは
15 [15/15]0xf840028220
[227 129 147 227 130 147 227 129 171 227 129 161 227 129 175]
15 こんにちは
15 んにちは
15 こんにちは

string to int

数字を表す文字列から数字を生成するに、

fmt.Sscanf()
が一番強力だが、軽くやりたければ strconv.Atoi を使う。
package main
import(
	. "fmt"
	"strconv"
)

func main(){
  var s = "123"
  var x int
  x,err := strconv.Atoi(s)
  Println(err,x)
  n,err := Sscanf(s, "%d", &x)
  Println(n,err,x)
}
output
<nil> 123
1 <nil> 123

一時変数

一時変数

一時変数は ":=" で定義される。(宣言と初期化を兼ねている)
次に、":=" の効果をみるプログラムを示す。
package main
func main(){
  x:=3  // temporal variable, use ":=" without "var".
  println(x)  // 3
  {
    x=x+2
    println(x)  // 5
    x:="abc"
    println(x)  // abc
  }
  println(x) // 5
}

コメント "//" の右に println() の出力が示されている。
同じ変数名 x を使っていても、実体は異なる変数であることが分かる。

for 文と一時変数

package main
func main(){
  var x int
  for x:=0; x <3; x++ {
    println(x)
  }
  println(x)	// o
  for x=0; x < 3; x++ {
    println(x)
  }
  println(x)	// 3
}

for 文のための制御変数のために、わざわざ var で変数宣言するのは嫌だから、":=" を導入したらしい。
しかし、伝統的な C のスタイルだと、繰り返しが最後まで行われたことが、容易に分かるメリットもある。

配列

宣言と要素への代入、要素の参照

package main
import "fmt"
func main(){
  var primes = [10]bool{}
  primes[2]=true
  primes[3]=true
  primes[5]=true
  primes[7]=true
  for i:=2; i<10;i++{
    fmt.Printf("%d:%-8v",i,primes[i])
  }
  fmt.Println()
}

array2.go

あいるは

package main
import "fmt"
func main(){
  var primes = [10]bool{
    2: true,
    3: true,
    5: true,
    7: true,
  }
  for i:=2; i<10;i++{
    fmt.Printf("%d:%-8v",i,primes[i])
  }
  fmt.Println()
}

配列の宣言法

[10]bool{}
で論理値( bool )を要素とする10個の配列を宣言し初期化する。配列のインデックスは 0 から 9 である。
" " で初期値を指定するが、この場合には指定されていないので、初期値は false となる。

Printf の特殊な書式

Printf の書式の中の "%v" は、変数の型に合わせて適当にやってくれる。"%s" や "%d" の代用になる。

なお Printf の書式の中の "%T" は、変数の型を表す。

実行結果

-bash$ go run array2.go
2:true    3:true    4:false   5:true    6:false   7:true    8:false   9:false
-bash$

宣言時の初期化

次に、配列のサイズを、初期値から決める例を挙げる。

package main
import "fmt"
func main(){
  var days = [...]string{"sun","mon","tue","wed","thu", "fri","sat"}
  for i:=0; i<len(days);i++{
    fmt.Printf("%5s",days[i])
  }
  fmt.Println()
}

配列のサイズ [...]

ここに現れる「...」は任意個を表す。

配列の初期値

初期値は、全て書く必要はない。初めの部分だけ与えて、残りを与えない書き方も可能である。与えられなかった部分は、デフォルトの値に設定される。

for 文の書き方

for の部分は、次のように、もう少し簡潔に書ける。
for _,v := range days {
    fmt.Printf("%5s",v)
}

実行結果

-bash$ go run array1.go
  sun  mon  tue  wed  thu  fri  sat
-bash$

array と slice

先のプログラムで
var days = [...]string{"sun","mon","tue","wed","thu", "fri","sat"}
の部分を
var days = []string{"sun","mon","tue","wed","thu", "fri","sat"}
と置き換えても同じように動く。前者の days の型は array で、後者の場合には slice である。slice と言うのは、データへのポインタである。この違いが具体的にどのような場合に発生するか?

package main
import "fmt"
func main(){
  var a = [...]string{"sun","mon","tue","wed","thu", "fri","sat"}
  var b = a
  a[1] = "lua"
  for i:=0; i<len(a);i++{
    fmt.Printf("%5s",a[i])
  }
  fmt.Println()
  for i:=0; i<len(b);i++{
    fmt.Printf("%5s",b[i])
  }
  fmt.Println()
}

array3.go

このプログラムでは b もまた array であり、a とは独立したデータを保有している。(a のデータがコピーされている。) 従って、a を変更しても b は変更されない。この事は次の結果で分かる。

-bash$ go run array3.go
  sun  lua  tue  wed  thu  fri  sat
  sun  mon  tue  wed  thu  fri  sat
-bash$

slice 型変数はデータの本体を保有しない。(実際には array へのポインタである。)
次のプログラムでは ab も共通のデータへのポインタなので、a を変更する(a を通じて本体を変更する)と、

package main
import "fmt"
func main(){
  var a = []string{"sun","mon","tue","wed","thu", "fri","sat"}
  var b = a
  a[1] = "lua"
  for i:=0; i<len(a);i++{
    fmt.Printf("%5s",a[i])
  }
  fmt.Println()
  for i:=0; i<len(b);i++{
    fmt.Printf("%5s",b[i])
  }
  fmt.Println()
}

array4.go

結果は次のように、b も変更される。

-bash$ go run array4.go
  sun  lua  tue  wed  thu  fri  sat
  sun  lua  tue  wed  thu  fri  sat
-bash$

部分配列

package main
import "fmt"
func main(){
  a := [4]int{}
  a[0] = 2
  a[1] = 3
  a[2] = 5
  a[3] = 7
  fmt.Println(a[1:3])  // [3 5]
}

この例では a[1:3] の値は [3 5] である。これは、a[1]a[2] の値である。a を配列とすると

a[n:m]
a の部分配列(a[n],a[n+1],...,a[m-1])が得られる。n,m は次の関係を満たさなくてはならない:
0 <= n <= m < len(a)

Python との違い

Python では配列要素の位置を、行末からも決める事が可能で、そのために負のインデックスが許される。
Go は Python から、インデックスの範囲を表す表現法を踏襲しているが、負のインデックスは認めなかった。配列の範囲をはみ出したインデックスはバグに起因する事が多いと考えたのだろう。
便利さよりも安全性を重視した結果だと思われる。

配列の結合

Golang では配列は slice として利用すべきものである。
配列ヘの要素の追加、複数の配列の結合は、slice に対して行われる。
次に例を示す。
package main
import (
	. "fmt"
)

func main() {
	var a = [3]int{2,3,5}
	var b = [2]int{7,11}
//	Println(append(a[:],b[:])) //NG
	Println(append(a[:],b[:]...)) //OK [2 3 5 7 11]
	Println(append(a[:],b[0],b[1])) //OK [2 3 5 7 11]
}
b[:]b[0:] と同じである。全体を slice として扱いたい場合には、この方が良いであろう。
b[:]... は奇っ怪なように感じられるが、これは配列の要素を丸ごと関数の引数として渡したい場合に便利である。またこのテクニックを知らないと困ることがある。例えばデバッグ用の dbgPrintln() を作りたいときなど..

拡大可能な配列

make([]int,5,100)

配列の構成に必要な要素の個数が、実行してみないと分からない場合がある。(そのようなケースはドキュメント処理では極く普通である。)
Python では、そのようなケースは List で切り抜けている。Go はあくまで伝統的な配列を使う。(速度が速い)

配列の大きさが必要に応じて変化するような配列を定義するには make() を使う。次のプログラムで make() の使い方と特性を示す。

package main
func main(){
  a := make([]int,5,100)
  a[0] = 2
  a[1] = 3
  a[3] = 5
  println("len:",len(a))
  a = append(a,7,11,13)
  println("len:",len(a))
  for i:=0; i<len(a);i++{
    println(a[i])
  }
}
このプログラムの
a := make([]int,5,100)
が、ここでのテーマである。整数配列 a のサイズを 5 として、100 まで大きくなる初期値を与えている。実際には、100 を超える事が許されるが、100 を超えるとメモリーの再配置が内部で行われる(そのために時間のロスが発生する)可能性があり、適切な初期値を与えた方が良い。
コンパイラの適当な初期値設定に任せるなら
a := make([]int,5)
とする。

指定された配列の初期値(ここでは5)を超える部分への追加は 関数 append() で行う。

プログラムの実行結果は次のようになる。

len: 5
len: 8
2
3
0
5
0
7
11
13

初期配列を超える要素(ここでは a[n] (n >4) )の参照は、append() でデータが与えられていない限り実行時のエラーになる。この仕様は安全性のために当然である。

配列の要素には暗黙の初期値(整数の配列の場合には 0)が存在する。つまり明示的に初期化されていない要素が式の中に使われてもエラーにならない。この仕様は危険なのであり、安全性のためには Lua のように、nil を返した方が良い。しかし、Go における配列の概念はCの配列と同じで、要素は型で示されているとおりである。そのために nil は返せない。そもそも整数型の変数には、初期化されたのか否かの印を付ける事ができないのである。

注: 実際の応用では、配列の初期値は 0 に設定される事が多いであろう。

make([]int,0)

一切の初期サイズの設定を拒否したいなら。次のようにすればよい。
これは make([]int,5) の特殊ケースである。

package main
import (
	. "fmt"
)

func main() {
	var a = make([]int,0)
	for k:=0; k <10; k++ {
		a = append(a[:],2*k)
	}
	Println(a)
}

output:

[0 2 4 6 8 10 12 14 16 18]

リスト(List)

リストは配列と似ているが、要素の挿入や削除が可能である。(その代わり、配列のように使った場合には、遅い)

注: Python ではリストが基本であり、array はモジュールによってサポートされている。Lua では、table を array の代わりに使い、リストはサポートされていない。(必要なら自分で作る)

リストは package によってサポートされている。package は

import "container/list"
でインポートされる。
詳しくは、
http://golang.org/pkg/container/list/
に載っている。

map

Go の map は Python の辞書に相当する。

map のキーを整数に限定すると、配列のように使える。

package main
func main(){
  var d = map[int]string{}
  d[1]="alice"
  d[2]="bob"
  d[6]="frank"
  println(len(d))
  for k,v := range d {
    println(k,v)
  }
}

結果:

3
1 alice
2 bob
6 frank

初期化

map の初期化は次のように { } の中で行ってもよい。書き方は Python と同じ。
package main
func main(){
  var d = map[int]string{1:"alice",2:"bob",6:"frank"}
  println(len(d))
  for k,v := range d {
    println(k,v)
  }
}

存在の確認

人の年齢を map に登録する場合に、キーを名前(文字列)、値を年齢(整数)に採るのが自然な考え方であろう。
登録の前に、データの二重登録を防ぐ目的で、既に map に登録されている同じ名前が既に存在するか否かの点検が要求される。
次のプログラムの
v,ok := d["david"]
の部分が "david" が登録されているか否かの確認に使える。

package main
func main(){
  var d = map[string]int{"alice":12,"bob":18,"carol":22}
  v,ok := d["alice"]
  println(v,ok) // 12, true
  v,ok = d["david"]
  println(v,ok) // 0, false
}

なお、map に登録されていないキーの要素の値は Zero value (整数の場合には 0, 文字列の場合は "", ポインタの場合には nil) となる。Zero value だけを見て、登録の有無を判断できないのは、この例をみても明らかである。

式の中での map の参照

存在しないキーを使ってもエラーにならない!

package main
func main(){
  var d = map[string]int{"alice":12,"bob":18,"carol":22}
  n := d["alice"] + d["david"]
  println(n) // 12, not error
  //n,ok := d["alice"] + d["david"] // error, assignment count
  //println(n,ok)
}

構造を持つ変数

2022/01/30

ここでは構造体 "struct" の使い方(宣言の方法、値の与え方など)を例題で示す:

// lesson to structured variables
package main

import (
	. "fmt"
)

var p [2] float64

type Point struct {
x,y float64
}

var q Point

type Person struct {
name string
age	int
}

var a1 = Person {"alice", 16}

type T struct {
name string
age int
etc U
}

type U struct {
mail string
tel	string
}

func main() {
	Println(p)
	p[0] = 2.1
	p[1] = 1.2
	//p[2] = 3.1	// this should be error
	Println(p)
	// which is same as:
	p = [2] float64 {2.1, 1.2}
	Println(p)

	Println(q)
	q.x = 2.1
	q.y = 1.2
	Println(q)
	// which is same as:
	q = Point{2.1, 1.2}
	Println(q)

	Println(a1)

	b := T{"bob",18,U{"bob@gmai.com","0123-456-789"}}
	Println(b)
}

output:

[0 0]
[2.1 1.2]
[2.1 1.2]
{0 0}
{2.1 1.2}
{2.1 1.2}
{alice 16}
{bob 18 {bob@gmai.com 0123-456-789}}
最後の行を見て分かる通り、Println(b) は構造体の深部をちゃんと書き出している。

無名フィールド

2022/03/27

package main

import (
	. "fmt"
)

type Foo struct{
	int
	x,y int
}

func main() {
	var a Foo
 	a.int = 5; a.x = 2; a.y = 3
	Println(a)
}

ここに現れた

type Foo struct{
	int
	x,y int
}

type Foo struct
int int
x,y int

の省略形である。従って最初のフィールドへの代入は
a.int = 5
のようになる。(「無名」と言うのは誤解を招く)

コマンドインターフェース

os.Args

次の例は、コマンド引数の処理を示す。

package main
import "os"
func main(){
  for n:=0; n < len(os.Args); n++ {
    println(n,os.Args[n])
  }
}

range 修飾

実はもう少し簡潔に書ける。

package main
import "os"
func main(){
  for n,v := range os.Args {
    println(n,v)
  }
}

compile

ファイル名を xxx.go とすると、コンパイルと実行は
go build xxx.go && ./xxx alice bob caro
のように行えば良い。これで実行ファイル xxx が生成される。

なお、実行ファイル xxx.go の生成をしないならば、

go run xxx.go alice bob carol
で引数を与える事ができる。ただし xxx.go の所で Go のソース・ファイルを複数個並べると、違った意味になるので注意。

実行ファイル xxx$GOPATH/bin の中に生成したければ

go install xxx.go
とすればよい。

大域変数の動的初期化

C の場合

#include <stdio.h>
int a = f(3); // compile error
int f(int x) {
  return 2*x;
}
int main(int argc,char **argv){
  printf("OK %d\n",a);
  return 0;
}

次のように、関数の中で初期化しなくてはならない。

#include <stdio.h>
int a;
int f(int x) {
  return 2*x;
}
int main(int argc,char **argv){
  a = f(3);
  printf("OK %d\n",a);
  return 0;
}

Python の場合

# a=f(3)	# error
def f(x):
  return 2*x
a=f(3)
print "OK",a

Python に限らず、大域変数が実行時に(関数の外で)初期化できることは、スクリプト言語の常識である。
ただし、a=f(3) が実行される時点で f(x) が定義されていなくてはならない。

Go の場合

package main
var a = f(3)
func f(x int) int {
  return 2*x
}
func main(){
  println("OK",a)
}

この仕組みを使うと、例えば、コマンド引数を基に大域変数を次のように初期化できる。

package main
import (
  "os"
  "strconv"
)
var b,err = strconv.Atoi(os.Args[1])
var a = f(b)
func f(x int) int {
  return 2*x
}
func main(){
  println("OK",a)
}

var の部分は次のように書いてもよい。

var (
   b,err = strconv.Atoi(os.Args[1])
   a = f(b)
)

flag package に応用の具体例が見られる。

コマンド引数の情報を、関数 main の引数ではなく、os.Args でプログラムに渡す設計にしたのは、このような事情があるのかも知れない。

ファイル

例題1. rd.Read(bs) ファイルの読み取り

次のプログラムは、cat コマンドをシミュレートする。cat コマンドは引数が存在しない場合には標準入力から読み取るのであるが、このプログラムでは、その部分が省かれている。さらに stat 情報のコピーも省かれている)
package main
import (
  "os"
  "log"
)
func main(){
  var wr = os.Stdout
  var bs []byte
  bs = make([]byte,4*1024)
  for _,f := range os.Args[1:] {
    rd,err := os.Open(f)
    if err != nil {
      log.Fatal(err)
    }
    for n,err := rd.Read(bs) ; n > 0; {
      if err != nil {
        log.Fatal(err)
      }
      wr.Write(bs)
      n,err = rd.Read(bs)
    }
    rd.Close()
  }
}

複数 package の import

この例では、複数の package をインポートしている。この部分は
import ( "os"; "log" )
と書いてもよい。一行に書いた時には、";" ではなく、"," で区切りたくなるが、";" にしたのは、例題のように、一つの package を一つの行に書かせたかったのだろう。もしも、"," で区切るルールにすると、package 名のあとに "," を書かなくてはならなくなる。

slice 変数の初期化 make()

slice 変数は次のように初期化する。
var bs []byte
bs = make([]byte,4*1024)
もちろん、この部分は
var bs = make([]byte,4*1024)
とも書ける。

変数 "_"

Go では、代入された変数が使用されない場合には、コンパイラがエラー扱いにする。(従ってコンパイルできない。) これは厳しすぎるケースもあるので、その場合を切り抜けるために、変数 "_" が準備されている。変数 "_" はゴミ箱のような変数で、ここに代入された値が使用されなくても、コンパイラは文句を言わない。

関数の外で定義された変数に関しては、それを利用していなくてもコンパイラは文句を言わない。

論理式

C と違い、論理式は論理値を持たなくてはならない。従って
if err != nil {
と書く必要がある。(nil でない値は true と見なされない)

log.Fatal()

log.Fatal() はエラーの理由をログファイルに書き出し、os.Exit(1) (異常終了) で終わる関数である。ログファイルが指定されていない場合には、標準エラーに出力する。

注意

この例では for 文のブロックの中に ":=" が使われている。具体的には

rd,err := os.Open(f)
の部分である。for 文のブロックの中における ":=" の使用はできるだけ避けるべきである。C とは異なり golang では ":=" で定義された変数は、for 文の条件部に現れる変数とは(たとえ同名の変数であっても)関係ない。そのために繰り返しの停止条件に関わる変数を for プロックの中で ":=" で与えると無限ループに陥ることがある。

例題2. bufio

こり例題では

defer
bufio.NewReader()
bufio.ReadString()
os.WriteString()
err.Error()
for
が扱われている。

package main
import (
  "os"
  "bufio"
  "log"
)

func parse(path string){
  var line string
  file,err := os.Open(path)
  if err != nil {
    log.Fatal(err)
  }
  defer file.Close()
  rd := bufio.NewReader(file)
  wr := os.Stdout
  line,_ = rd.ReadString('\n')
  for len(line) > 0 {
    // do something here, for example:
    wr.WriteString(line)
    line,_ = rd.ReadString('\n')
  }
}

func main(){
  parse(os.Args[1])
}

修飾子 defer

関数 parse() の中の

defer file.Close()
は、defer に続く関数が parse() の終了時に実行される事を指定している。defer で指定される関数は「後始末関数」である。エラー処理の後にファイルをクローズするのを忘れがちになるのだが、defer を使えば、そうしたバグから解放される。

defer は複数指定してもよく、後に指定した後始末関数から順に実行される。

bufio.ReadString()

次に "bufio" の使用例を示す。
テキストファイルを読んで、一行毎に解析を行いたい場合には ReadString が便利である。この関数は、一行の文字数があらかじめ分かっていない(つまり非常に長い行が含まれている可能性がある)場合に使われる。

ReadString() が返す値はファイルに含まれる 1行の文字列であり、行の区切り文字(このケースでは '\n') を末尾に含む。
UNIX の伝統的なテキストファイルの形式では、ファイルに含まれる最後の文字は '\n' である。しかし、この形式に従わないテキストファイルが存在するので注意が必要である。

bufio.ReadString() は Plan9 の readstr() の Go での実装である。readstr() は C の関数であるために、得られた行は明示的に free() でメモリを解放する必要があった。しかし、bufio.ReadString() の場合は、メモリの解放はガーベッジコレクタが行っているはずである。

bufio.ReadLine()

一行を読み取る関数としては他に ReadLine() があるが、こちらは(メモリ効率は良いが)運用に次の注意が要求される。
つまり、ReadLine() はあらかじめ一行の最大サイズが分かっていない場合に使うとバグを発生させるか、あるいは面倒な処理が要求される。また得られた行を蓄積して、ファイルを読み終わってから解析するわけには行かない。
こうした事情があるために、Python のような言語では ReadLine() 相当の関数はサポートされないのであるが、Go は C の細やかさも追求しようとしているのだろう。( Go の ReadString() は Python の readline() に相当する )

注1: この問題は ReadSlice() で解決されている。

for 文の他の書き方

ここでは
for len(line) > 0 {
となっている。つまり
for 論理式 {
の形が許される。つまり、実質的な while 文である。

もちろん、この部分は

for ; len(line)>0; {
と書いてもよい。

テキストファイルが '\n' で終わらないケース

次のプログラムは、'\n' で終わらないテキストファイルに対しては、'\n' を追加する。

package main
import (
  "os"
  "bufio"
  "log"
)

func parse(path string){
  var line string
  file,err := os.Open(path)
  if err != nil {
    log.Fatal(err)
  }
  defer file.Close()
  rd := bufio.NewReader(file)
  wr := os.Stdout
  line,err = rd.ReadString('\n')
  for err == nil {
    // do something here, for example:
    wr.WriteString(line)
    line,err = rd.ReadString('\n')
  }
  if len(line) > 0 {
    wr.WriteString(line+"\n")
  }
}

func main(){
  parse(os.Args[1])
}

読み取った行のサイズが正であるにも係わらず、err が発生するのは '\n' で終わっていないからである。このケースでは err == io.EOF となっている。

例題3. os.ReadFile

2022/01/27

普通のファイル、つまりファイルに含まれる文字数が判っているファイルでは、その情報を利用してファイルを読み取ることができる。この場合に簡便なのは os.ReadFile() である。次に例を示す。

package main
import(
	. "fmt"
	"os"
)

var usage = "usage: read1 file"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

// look src/os/file.go for os.ReadFile()

func main(){
	args := os.Args[1:]
	if len(args) != 1 {
		Error(usage)
	}
	b,err := os.ReadFile(args[0])
	if err != nil{
		Error(err)
	}
	Print(string(b))
}

ファイルの内容を全てメモリーに蓄えるので、大きなファイルには向かない。
読み取った結果をメモリーに蓄えたいのだが、予め必要なメモリーのサイズが判らないことがある。例えば標準入力からの読み取りの場合である。そのような場合には os.ReadFile は役に立たない。しかし io.ReadAll(rd) が使える(次を見よ)。 この例は Hget でも使われている。

なお関数 Error() における interface{} については「型無し変数」を見よ。

io.ReadAll

package main

import(
	. "fmt"
	"os"
	"io"
	"crypto/sha256"
)

var usage = "sha256sum [file]"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func main(){
	var err error
	var bs []byte
	args := os.Args[1:]
	rd := os.Stdin

	switch(len(args)){
	case 0:
	case 1:
		f := args[0]
		rd,err = os.Open(f)
		if err != nil {
			Error(err)
		}
	default:
		Error(usage)
	}
	bs,err = io.ReadAll(rd)
	if err != nil {
		Error(err)
	}
    rd.Close()
	sum := sha256.Sum256(bs)
	Printf("%x\n",sum)
}

複数のファイルが指定された場合にも対応すべきだが、サボられている。

型無し変数

Interface

次に示すのは、任意のデータを代入できる変数(型無し変数)の宣言法を示すプログラムである。
package main
import "fmt"
type typeless interface{}
func main(){
  var x typeless
  x = 3
  println(x)
  println(x.(int)) 	// 3
  fmt.Println(x)	// 3
  x = "alice"
  println(x)
  println(x.(string))	// alice
  fmt.Println(x)	// alice
}
このプログラムでは、変数 x は、整数 3 の代入を許したり、文字列 "alice" の代入を許したりしている。このような柔軟な変数はインターフェース型変数の特殊ケースであり、型名を
var x interface{}
として宣言する。

注: このプログラムでは、interface{}typeless として定義している。これは僕の好みである。

型無し変数と演算

このプログラムで定義されている typeless 変数は、演算の対象にならない。例えば
var x typeless
x = 2
println(x + 3)		// error
はダメである。
var x typeless
x = 2
println(x.(int) + 3)	// OK
のように、明示的に型を指定する必要がある。Python などに比べて面倒だと思うかも知れないが、この厳格さが誤りを防ぐとする考え方もある。

関数

関数の戻り値

2022/02/10

Golang では Python (あるいは現代的な言語)のように複数の戻り値を指定できる。これは非常にありがたいのである。2つの方法を関数 f(x,y) と関数 g(x,y) で示す。関数 f(x,y) は普通のスタイルである。他方 g(x,y) は他の言語に慣れているとちょっと奇っ怪であるが、エラーコードを返すのに便利なように設計されている。次にそのサンプルを示す。

package main

import (
	. "fmt"
	"os"
)

var usage = "usage: a1"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func f(x int,y int) (int, int) {
	return x+y, x*y
}

func g(x int, y int) (sum int, prod int) {
	sum = x+y
	prod = x*y
	return
}

func main() {
	Println(f(2,3)) // output: 5 6
	Println(g(2,3)) // output: 5 6
}

/* look
https://go.dev/ref/spec
for details
*/

可変個数の引数の関数

以下に可変個数の引数の関数の定義例
package main
func f(args ... int){
  for k,v := range args{
    println(k,v)
  }
}
func main(){
  f(2,3,5)
}
結果は
0 2
1 3
2 5
宣言
args ... int
argsint 型の可変個数引数である事を示している。

この例では可変個数部分の全てが単一タイプ( int )であるが、型の部分も含めて自由にデータを関数に渡したい場合にはどうするか?

次のプログラムがヒントになる。

package main
import "fmt"
func f(args ... interface{}){
  for k,v := range args{
    println(k,v)
    fmt.Println(k,v)
  }
}
func main(){
  f(2,3,"alice")
}
この出力は
0 (0x56820,0xf800000002)
0 2
1 (0x56820,0xf800000003)
1 3
2 (0x56a60,0xf840028220)
2 alice

error の返し方

package main
import(
	. "fmt"
	"errors"
)

// $GOROOT/src/errors/errors.go
func main(){
	err := errors.New("blah blah")
	Println(err)
}

Pointer と Slice 変数

2022/02/10 更新

基礎

ポインタ(pointer)は C と同様にメモリーのアドレスである。ポインタにおける "*" と "&" の使い方は C の方法を踏襲している。

他の言語に比べて C ではポインタを使えることが強力な武器になったが、他方では(コーディングミスによる)危険性を孕んでいた。この問題は、配列へのポインタとして slice 型を導入することによって回避している。

slice 変数とは(与えられた)配列の範囲を表す情報を含む変数で (ポインタ、長さ、容量) の3つから構成されている1。例えば a を長さ7の配列とすると a[2:5]a[2],a[3],a[4] から構成される a の部分配列が得られるが、このときのポインタは a[2] を指しており、長さは 3 、容量は 7 である。容量は許容されるインデックスの範囲を知るのに利用される。

Slice 変数は関数に部分配列を渡すのに使われることが多い。ポインタ変数も関数引数に使われることもあるが、使い方の例を次に示す。

package main

import (
	. "fmt"
	"unsafe"
)

func f(a []int){
	Println(a)
}

func g(a *[]int){
	Println(*a)
}

func main() {
	var n int
	var a [7]int
	var b []int
	n = 1
	for k:=0; k<7; k++ {
		n *= 2
		a[k] = n
	}
	b = a[2:5]
	Println(unsafe.Sizeof(b)) // output: 24
	Println(b) // output: [8 16 32]
	f(b) // output: [8 16 32]
	g(&b) // output: [8 16 32]
	// g(&a[2:5]) // NG
}

この output は

24
[8 16 32]
[8 16 32]
[8 16 32]
である。
プログラムの中の unsafe.Sizeof(b) はデバッグ用の特殊な関数で b のサイズを知るのに使われる。もちろん CPU に依存する。unsafe にはこの他 CPU 依存性を持ったいろいろなデバッグ用の関数が存在する。

f(b)f(a[2:5]) と置き換えても構わないが、g(&a[2:5]) は許されない。slice 変数は十分に軽量であるから、敢えてポインタで渡す必要はないのである。


注1: アラン・ドノバン『プログラミング言語 Go』

look alse https://go.dev/ref/spec

配列のコピー

copy が準備されている。
slice 同士でコピーを行う。
package main

import (
	. "fmt"
)

func main(){
	var a = [5]int{2,3,5,7,11}
	var b []int
	var c [5]int
	var n int
	c = a
	Println(a,c) // [2 3 5 7 11] [2 3 5 7 11]
	n = copy(b,a[1:4])
	Println(n,a,b) // 0 [2 3 5 7 11] []
	n = copy(c[3:],a[1:4])
	Println(n,a,c) // 2 [2 3 5 7 11] [2 3 5 3 5]
	n = copy(a[3:],a[1:4])
	Println(n,a) // 2 [2 3 5 3 5]
}

正規表現

2022/01/14
2022/02/11 追加

Go の正規表現は Perl, Python などの unix でポピュラーなスクリプ言語のシンタックスを受け継いでいるが、これらの「正規表現」には問題があるとのことで[2]、全てがサポートされてはいない。詳しくは

go doc regexp/syntax
を実行するか、または文献[1]を見よ。さらに
$GOROOT/src/regexp/regexp.go
のコメントにも詳しい解説がある。

プログラムサンプル

次に示すのは Go で書いた grep である。ただし flag や option はサポートされていない。
unix の grep は貧弱であり egrep の方が使いやすい。Go の正規表現は egrep の正規表現に近い。

package main

import (
	. "fmt"
	. "regexp"
	"os"
	bio "bufio"
)

var usage = "usage: grep expression [file ...]"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func grep(re *Regexp, fd *os.File){
	var m bool
	rd := bio.NewReader(fd)
	line,err := rd.ReadString('\n')
	for err == nil {
		m = re.Match([]byte(line))
		if m {
			Print(line)
		}
		line,err = rd.ReadString('\n')
	}
	if len(line) > 0 { // input doesn't end with '\n'
		Println(line)
	}
}

func main(){
	args := os.Args[1:]
	if len(args) == 0 {
		Error(usage)
	}
	expr := args[0]
	re, err := Compile(expr)
	if err != nil {
		Error(err)
	}
	// Printf("re: %T\n",re) // *regexp.Regexp
	args = args[1:]
	if len(args) == 0 {
		// Printf("Stdin: %T\n",os.Stdin) // *os.File
		grep(re, os.Stdin)
		os.Exit(0)
	}

	var fd *os.File
	n := len(args)
	for k:=0; k < n; k++ {
		fd, err = os.Open(args[k])
		grep(re, fd)
		fd.Close()
	}
}

grep.go

ここに現れた

rd.ReadString('\n')

re.Match([]byte(line))
は、「対象」 (rdre) に対する作用を表しているシンタックスで、Python ではよく使われる。しかし Python ではいわば便法で、「対象」の部分は関数引数に移せるが、golang は違う。例えば
Match(expr, []byte(line))
とすると違う意味になる。この場合 "expr" は string で、これを基に Compile が行われ、結果が re.Match([]byte(line)) に渡される。1回きりの場合にはそれでも良いが、この例のように何回も実行される場合には、効率が悪くなる。

これらの使い方は、Go のソースコードを直接調べなくても

hebe% go doc regexp.Match
package regexp // import "regexp"

func Match(pattern string, b []byte) (matched bool, err error)
    Match reports whether the byte slice b contains any match of the regular
    expression pattern. More complicated queries need to use Compile and the
    full Regexp interface.
hebe% go doc bufio.ReadString
package bufio // import "bufio"

func (b *Reader) ReadString(delim byte) (string, error)
    ReadString reads until the first occurrence of delim in the input, returning
    a string containing the data up to and including the delimiter. If
    ReadString encounters an error before finding a delimiter, it returns the
    data read before the error and the error itself (often io.EOF). ReadString
    returns err != nil if and only if the returned data does not end in delim.
    For simple uses, a Scanner may be more convenient.
hebe%
でも判るはずであるが1、なぜか Match については完全ではない。実は Matchregexp.go の中に2つある:
func (re *Regexp) Match(b []byte) bool
func Match(pattern string, b []byte) (matched bool, err error)
しかしこのうちの1つしか表示していないのである2。バグでしょうね3

このプログラムを書いていて気がついたことを記す。
import の中で

bio "bufio"
となっていのは、
. "bufio"
とすると、「"bufio" はもう "fmt" で import されている」と文句を言われたからである。そこで "." ではなく "bio" で切り抜けた次第である。この部分は少し不自然なので、もっと良い方法があるかも知れない。

プログラムの中で

// Printf(....)
のようにコメントアウトされている箇所が2箇所あるが、関数 grep を作る際に、変数の型を調べる必要があったからである。

なお関数 grep

func grep(re *Regexp, fd *File){
	var m bool
	rd := bio.NewReader(fd)
	line,err := rd.ReadString('\n')
	for err == nil {
		m = re.Match([]byte(line))
		if m {
    		Print(line)
		}
    		line,err = rd.ReadString('\n')
	}
}
とした場合には、改行コードを含まない「行」が grep で引っかからない。例えば
echo -n alice | ./grep ali
を試してみたらよい。

昔のようにテキストファイルが必ず改行コードで終わる時代は過ぎ去った。現在主に使われているテキストエディタは行エディタではなく文字エディタである。そしてコンピュータの利用者の裾野が広がった結果、改行コードで終わらないテキストファイルが氾濫している。そのようなテキストファイルに対しても正しく動くプログラムが求められる時代になっている。


注1: "hebe%" はプロンプトである
注2: golang のソースコードの置き場所は
go env GOROOT
で判る。環境変数 "$GOROOT" がここに設定されているはずである。(設定されていなければ設定すべし)
ここで取り上げた regexp.go
$GOROOT/src/regexp/
に置かれている。従って
egrep '^func.* Match\(' $GOROOT/src/regexp/*.go
とすれば見落とすことが無かったであろう。
注3: 開発者に質問したらバクではないという。それ以上の説明はなかった。

参考文献

[1] Syntax
https://github.com/google/re2/wiki/Syntax

[2] Russ Cox: Regular Expression Matching Can Be Simple And Fast
https://swtch.com/~rsc/regexp/regexp1.html

Net

2022/01/17

Dial

net package にある net.Dial を使った簡単なプログラム例を示す。
package main

import (
	. "fmt"
	"os"
	"net"
	bio "bufio"
)

var args []string

var usage = "usage: dial addr:port (addr is sysname or IP address)"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func main() {
	args = os.Args
	if len(args) != 2 {
		Error(usage)
	}

	conn, err := net.Dial("tcp", args[1])
	if err != nil{
		Error(err)
	}
	rd := bio.NewReader(conn);
	b, err := rd.ReadString('\n')
	for ; err == nil; {
		Print(string(b))
		b, err = rd.ReadString('\n')
	}
	if len(b) > 0 {
		Println(string(b))
	}
}

dial.go

このプログラムは相手サーバーに tcp で接続して、相手の応答を返す。例えば onamae.com は接続したクライアントの IP アドレスを教えてくれる:

dial ddnsclient.onamae.com:65000
に対する応答は(例えば)
IPV4: 123.48.102.187
.
のようなものである。これは我が家のホーム GW の WAN 側の IP アドレスである。WAN 側の IP アドレスは、ホーム GW に直接アクセスしても判るが、面倒である。外部のサーバーから教えてもらった方が速い。ネット上にはこのようなサービスを行っているサーバーがいくつか存在する。僕がこのようなものを必要としているのは、我が家は固定 IP ではなく、時々 IP が変化する(料金の安い)動的 IP でプロバイダーと契約しているからである。

我が家のサーバー hebe も同様なサービスを、家庭内のクライアントに対して行っている。例えば

meg% dial hebe:8007
192.168.0.6!8007
192.168.0.10!47686
meg% dial hebe:8008
1642213304
meg%
のように、port 8007 に対してはサーバーとクライアントの IP と port を、port 8008 に対しては現在時刻(unix time)を知らせる。サーバー hebe の現在時刻はインターネットの時刻サービを参照しているので、実用上十分な精度を持っているはずである。家庭内の他のサーバーはこれを参照することになっている。

譜の dial.go はいくつかの問題がある。
(a) IPv4 と IPv6 の選択ができない
(b) 柔軟性を欠いている

Listen

次に listener のプロトタイプを示す。指定されたポート 8000 にアクセスすると、"Hello World" を表示するだけの、実用的価値のないサーバーである。
package main

import (
	. "fmt"
	"os"
	"net"
	"io"
)

var usage = "usage: srv1"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

//https://pkg.go.dev/net#example-Listener
func handleConn(conn net.Conn){
	_, err:= io.WriteString(conn,"Hello World\n")
	if err != nil {
		Fprint(os.Stderr,"#Error ",err)
	}
	conn.Close()
}

func main() {
	ln, err := net.Listen("tcp", ":8000")
	if err != nil {
		Error(err)
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			Error(err)
		}
		//Fprintf(os.Stderr,"%T\n",conn) // *net.TCPConn
		go handleConn(conn)
	}
}

srv1.go

ln.Accept() の所で for ループになっているのは、クライアントから何回でもアクセスされるからである。

解せない箇所がある。ln.Accept() が返す conn の型を調べると *net.TCPConn となっている。ところが

func handleConn(conn *net.TCPConn){
と書くと、コンパイラが文句を言う。ここは conn net.Conn でなくてはならないと。

srv1 は次のように実行される。

meg% go build srv1.go
meg% srv1&
meg% dial meg:8000
Hello World
meg%

今の場合には handleConn の負荷が小さい。そして dial は意地悪をしていない。このケースでは

go handleConn(conn)
の代わりに単に
handleConn(conn)
としても問題はない。しかし一般的に言えば handleConn が直ちに終了する保証はない。go を付加したのは handleConn(conn) を接続ごとに並列的に動作させたいからである。内部では C のfork() 関数あるいは thread が使われているはずである。これを Goroutine と言う。

今度は srv2.go として、アクセスしたときに、ホスト側のアドレスとポート、およびクライアント側のアドレスとポートを教えてくれるサーバーを作成する。srv1.go からの変更点はただ一つ

_, err:= io.WriteString(conn,"Hello World\n")

_, err:= io.WriteString(conn,Sprint(conn.LocalAddr(),"\n",
	conn.RemoteAddr(),"\n"))
置き換えることだけである。

僕のように C に慣れきっている人間は、ここで手が止まってしまう。Sprint が生成したメモリーはどうなるのだ? このメモリーを開放できないではないか? と。C 言語であればアウトである。しかし Go はゴミ取りをやってくれる。ゴミ取りが正しく働いていれば WriteString が終了した段階でメモリーを開放してくれるはずである。Go を信頼しましょう。

最後に srv3.go として、現在の unix time を教えてくれるサーバープログラムを作成する。srv1.go との変更点は次の2点:

_, err:= io.WriteString(conn,"Hello World\n")
の代わりに
_, err:= io.WriteString(conn,Sprint(time.Now().Unix()))
とすることと、import の中に "time" を含めること
だけである。

補注: srv2.go のプログラム、なぜか Plan9 では動作が変である。Go のバグの可能性が高い。Linux では問題ない。なお Plan9 で同じことを行うには、数行のシェルスクリプトで足りる。ファイル t1

#!/bin/rc
cat $net/local
cat $net/remote
として
aux/listen1 'tcp!*!8000' t1 &
を実行すればよい。

文献

[1] Documentation
https://pkg.go.dev/net

HTTP

2022/01/07

Hget

package main

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

var usage = "usage: hget1 url"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func main() {
	args := os.Args
	if len(args) != 2 {
		Error(usage)
	}
	resp,err := http.Get(args[1])
	if err != nil {
		Error(err)
	}
	defer resp.Body.Close()
	body,err := io.ReadAll(resp.Body)
	if err != nil {
		Error(err)
	}
	Print(string(body))
}

hget1.go

詳しくは文献[1]を見よ。

hget よりも丁寧なエラーメッセージを出している


注1: https://pkg.go.dev/net/http からの引用:
Starting with Go 1.6, the http package has transparent support for the HTTP/2 protocol when using HTTPS.

Web server

次に基礎的な Web サーバーのコードを示す。

package main

import (
	. "fmt"
	"os"
	"net/http"
)

var usage = "usage: hsrv1 dir [port]"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

/*
Ref:
	$GOROOT/src/net/http/example_test.go:60
for this code
*/
func main() {
	var port = "80"
	// Simple static webserver:
	args := os.Args[1:]
	if len(args) == 0 || len(args) > 2 {
		Error(usage)
	}
	dir := args[0]
	if len(args) == 2 {
		port = args[1]
	}
	err := http.ListenAndServe(":"+port, http.FileServer(http.Dir(dir)))
	if err != nil {
		Fprintln(os.Stderr, err);
	}
}

hsrv1.go

文献[1]と[2]を参考にしたが、いくつか修正されている。修正点は

CGI を使わないなら、これで十分実用的な personal Web サーバーになっている。

文献[2]では

log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
になっていたが、この中の port と "/usr/share/doc" を起動時に引数として与えるようにしたのと、log.Fatal は止めて、直接 err の内容を見れるようにしただけである。
log.Fatal を使いたいなら
	if err != nil {
		Fprintln(os.Stderr, err);
		log.Fatal(err)
	}
とすれば良い。

Go の開発者たちはセキュリティに敏感である。指定された document root からアクセスがはみ出さないように細心の注意が払われていると想像する。(彼らの開発した Plan9 の Web サーバーのコードを見ると、そうであった)

このサーバーでは

となっている。

問題点:

CGI のサポート

CGI をサポートしたい場合には次のようになる:

package main

import (
	. "fmt"
	"os"
	"io"
	"net/http"
	"html"
	bio "bufio"
)

type typeless interface{}

var usage = "usage: hsrv2 dir [port]"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func fooHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "こんにちは!\n")
}

func barHandler(w http.ResponseWriter, r *http.Request) {
	Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path))
}

var dir = ""

/*
look
	https://pkg.go.dev/net/http
	$GOROOT/src/net/http/example_handle_test.go
	$GOROOT/src/net/http/example_test.go:68
for http.Handle and http.HandleFunc
*/
func main() {
	var port = "80"
	args := os.Args[1:]
	if len(args) == 0 || len(args) > 2 {
		Error(usage)
	}
	dir = args[0] // document root
	if len(args) == 2 {
		port = args[1]
	}
	if dir[0] != '/' {
		Error(`need dir to start with "/"`)
	}
	if len(dir) == 1 {
		Error(`dir must not be "/"`)
	}

	http.HandleFunc("/foo", fooHandler)
	http.HandleFunc("/bar", barHandler)
	http.Handle("/", http.FileServer(http.Dir(dir)))

	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		Fprintln(os.Stderr, err);
	}
}

hsrv2.go

Handler は用途に応じて各自コードを修正すること。

文献

[1] Documentation
https://pkg.go.dev/net/http

[2] $GOROOT/src/net/http/example_test.go

HTTPS

2022/01/18

https サーバー

package main

import (
	. "fmt"
	"flag"
	"os"
	"io"
	"net/http"
	"html"
)

type typeless interface{}

var usage = "usage: hsrv3 -c cert.pem -k key.pem dir [port]"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func fooHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "こんにちは!\n")
}

func barHandler(w http.ResponseWriter, r *http.Request) {
	Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path))
}

var dir = ""

func main() {
	var port = "443"
	cert := flag.String("c","","cert.pem")
	key := flag.String("k","","key.pem")
    flag.Parse()
	if *cert == "" || *key == "" {
		Error(usage)
	}
	args := flag.Args()
	if len(args) == 0 || len(args) > 2 {
		Error(usage)
	}
	dir = args[0] // document root
	if len(args) == 2 {
		port = args[1]
	}
	if dir[0] != '/' {
		Error(`need dir to start with "/"`)
	}
	if len(dir) == 1 {
		Error(`dir must not be "/"`)
	}

	http.HandleFunc("/foo", fooHandler)
	http.HandleFunc("/bar", barHandler)
	http.Handle("/", http.FileServer(http.Dir(dir)))

	err := http.ListenAndServeTLS(":"+port, *cert, *key, nil)
	if err != nil {
		Fprintln(os.Stderr, err);
	}
}
/*
example
in one window
	hsrv3 -c cert.pem -k key.pem $home/www/doc 8443
in another window
	hget2 -f https://meg.local:8443
*/

hsrv3.go

実行例はコードの中にコメントとして与えられている。meg.local は実験用のサーバーである。cert.pemkey.pem の作り方は次の Certificate に示す。

Certificate

自己証明書は次のように作成できる。

gcert=$GOROOT/src/crypto/tls/generate_cert.go
go run $gcert -host meg.local

gencert

ここに meg.local はサーバーのドメイン名で、各自の環境に応じて設定する。
何回も実行する必要がないので、go run で充分だろう。

Ref:
https://go.dev/src/crypto/tls/generate_cert.go

Hget for https

今度の hget は、相手が自己証明の場合に対応している。force flag "-f" がそれである。このフラグを与えてない場合には hget1.go と同じ動作になるはずである。

package main

import (
	. "fmt"
	"os"
	"io"
	"net/http"
	"crypto/tls"
)

var usage = "usage: hget2 [-f] url"

func Error(s interface{}){
	Fprintln(os.Stderr,s)
	os.Exit(1)
}

func main() {
	var force = false
	args := os.Args[1:]
	if args[0] == "-f" {
		force = true
		args = args[1:]
	}
	if len(args) != 1 {
		Error(usage)
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: force},
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Get(args[0])
	if err != nil {
		Error(err)
	}

	defer resp.Body.Close()
	body,err := io.ReadAll(resp.Body)
	if err != nil {
		Error(err)
	}
	Print(string(body))
}

hget2.go

通信のトランスポート層に対する制御は

tr := &http.Transport{
	TLSClientConfig: &tls.Config{InsecureSkipVerify: force},
}
のように行っている。

次は hget2.go を作成するにあたって、僕が調べたことのメモ。

for http.Client look

https://github.com/golang/go/blob/master/src/net/http/transport.go
we have:
type Transport struct {
...
TLSClientConfig *tls.Config
...
}

and in https://pkg.go.dev/crypto/tls, we have:

type Config struct {
...
InsecureSkipVerify bool
...
}

コードを書くにあたって、記事[2]が参考になった。
https://stackoverflow.com/questions/12122159/how-to-do-a-https-request-with-bad-certificate
基になっている文献は[1]にある。

文献

[1] Documentation
https://pkg.go.dev/net/http#Client.Get

[2] How to get x509 Certificate from http Client in Go
https://stackoverflow.com/questions/12122159/how-to-do-a-https-request-with-bad-certificate