Open7

Golangのポイントまとめ

ピン留めされたアイテム

思想、方針等

Goが作られた背景

  • Goは2007年にGoogleによって開発された
  • Goが開発された当時に広く使われていた言語は現在では当たり前となっているマルチコアプロセッサーやインターネットへのアクセスが世界的に提供される以前の時代に開発されたものであり、こうした現代では当たり前となった(そしてCloud上でのアプリケーション構築で必須となる)ものを効率的に使うことができない
  • こうした状況やエンジニアリング上の問題を踏まえて、Cloud Nativeな世界で必要とされる特性を備えた言語としてGolangを開発した

The Go programming language was conceived in late 2007 as an answer to some of the problems we were seeing developing software infrastructure at Google. The computing landscape today is almost unrelated to the environment in which the languages being used, mostly C++, Java, and Python, had been created. The problems introduced by multicore processors, networked systems, massive computation clusters, and the web programming model were being worked around rather than addressed head-on. Moreover, the scale has changed: today's server programs comprise tens of millions of lines of code, are worked on by hundreds or even thousands of programmers, and are updated literally every day. To make matters worse, build times, even on large compilation clusters, have stretched to many minutes, even hours.

Go was designed and developed to make working in this environment more productive. Besides its better-known aspects such as built-in concurrency and garbage collection, Go's design considerations include rigorous dependency management, the adaptability of software architecture as systems grow, and robustness across the boundaries between components.
Go at Google: Language Design in the Service of Software Engineering

Goの作者であるロブ・パイクが上記で述べているようにそうした状況に対する対処として、Googleのような大規模な組織でも生産性を高くするために開発された。そうした実際の問題解決としてGolangの思想は非常に魅力的に見える。

Goのゴールは、2種類のScale ―システムのスケーラビリティと開発のスケーラビリティ― であると、Russ Coxは「Toward Go 2」という記事で述べています。

この2つを実現する上で大事なキーワードが、Simplicityです。Goの言語としての優位性は、シンプルなものを組み合わせたバランスの良さにあります。
「Go言語らしさ」とは何か? Simplicityの哲学を理解し、Go Wayに沿った開発を進めることの良さ - エンジニアHub|Webエンジニアのキャリアを考える!

参考資料

GoとOOP

Goは伝統的なオブジェクト指向言語ではない。
提供されている機能を使えばメソッドを持たせるなどオブジェクト言語らしい機能を作ることもできるが、継承(Inheritance)ではなく、Compositionで複数の要素を組み合わせる。

これがStructsを学ぶ上でも基本となる。

encapsulation

GoではEncapsulationはExported and Unexported Identifiersによって実現している。
Golangでは先頭が大文字であればPublicになり、先頭が小文字であればPrivateになる。

ただし、先頭を小文字にしたままでもアクセスする方法はあり、getterやsetterのような関数を作ることで間接的にアクセスすることはできる。

Understanding Encapsulation in Go | by Sagar Sonwane | Medium

Go is Call By Value

Functionに変数を渡した時、Goでは常に渡した変数の値のコピーがとられる。
そのため、基本的に渡された変数を変更しても元のオブジェクトの値は変わらないが、例外的にMapとSliceは違う挙動を取る。
これは後述するpointersを使っているため。

以下に記載したとおりCall By Valueにはメリットがあるが、いくつかのユースケースでは渡された変数を直接変更する必要が出てくる。そういったケースではPointersを使う。

  • Call By Valueのメリット
    • Call by value is one reason why Go’s limited support for constants is only a minor handicap. Since variables are passed by value, you can be sure that calling a function doesn’t modify the variable whose value was passed in (unless the variable is a slice or map). In general, this is a good thing. It makes it easier to understand the flow of data through your program when functions don’t modify their input parameters and instead return newly computed values.

    • 値による呼び出しは、Goの定数のサポートが限られていることが小さなハンディキャップに過ぎない理由の1つです。変数は値で渡されるので、関数を呼び出しても、値が渡された変数は変更されないことが確認できます(変数がスライスやマップでない限り)。 一般的には、これは良いことだと思います。関数が入力パラメータを変更せず、新たに計算された値を返すことで、プログラム内のデータの流れを理解しやすくなります。
      > DeepLによる翻訳

色々なケース

Structの場合

変更しても何も反映されない。これがGoではデフォルトの挙動(Call By Value)

type person struct {
	age  int
	name string
}

func modifyFails(i int, s string, p person) {
	i = i * 2
	s = "Goodbye"
	p.name = "Bob"
}

p := person{}
i := 2
s := "hello"
// 値の変更に失敗する
modifyFails(i, s, p)
fmt.Println(i, s, p) // 2 hello {0 }

Mapの場合

Functionの中で加えられたすべての変更が適用される

func modMap(m map[int]string) {
	m[2] = "hello"
	m[3] = "goodbye"
	m[4] = "goodbye"
	delete(m, 1)
}

// mapの場合
m := map[int]string{
    1: "first",
    2: "second",
}
modMap(m)
fmt.Println(m) // map[2:hello 3:goodbye 4:goodbye]

Sliceの場合

要素の変更は適用される。ただし、要素数の変更(要素の追加や削除)はできない。

func modSlice(s []int) {
	for k, v := range s {
		s[k] = v * 2
	}
	s = append(s, 10)
}

// sliceの場合
s2 := []int{1, 2, 3}
modSlice(s2)
fmt.Println(s2) // [2 4 6]

Pointers

Pointerの使いドコロ

Pointer Typeを引数の型として使うことで、Functionによって対象の引数の値が変えられる可能性があることを示せる。

これはGoがCall By Valueの言語であることとも関連している?
Go is Call By Valueに記載されている通り、基本的にGoではFunctionの中で引数として受け取った変数を変えても値は変わらないが、Pointerを使う場合には値が書き換えられる可能性がある。

その可能性を明示する意味で、Pointerを使うことができる。

Pointerの概念

Pointerとは値が格納されているメモリー上の場所を持つ変数のこと

変数がメモリに格納される仕組み

例えば以下の変数定義を行ったとすると。

var x int32 = 10
var y bool = true

すべての変数は連続したメモリー上に配置される。(これをaddressesという)
そして、異なる型の変数はそれぞれで異なる量のメモリーを必要とする。
例えば32bit intは4バイトを必要とし、booleanは1バイトを必要とする(厳密にはbooleanは1bitだけで良いが、addressesとして使えるのは最低でも1バイトなので、1バイトを使う)

Pointerの仕組み

Pointerは前述の通りaddressを値として持つ(つまり他の変数に対する参照を行う)変数のこと。

var x int32 = 10
var y bool = true
pointerX := &x
pointerY := &y
var pointerZ *string

  • 変数は型によって必要なメモリの量が異なるが、Pointerは常に同じ
  • Pointerの Zero Valueはnil(slice, map, functionと同じ)。
  • slice, map, functionはpointersを使って実装されている。(まだ出てきていないchannelsとinterfacesも)
  • GoのPointerの文法はCとC++からとっているが、GoではGarbage Collectorがあるのでメモリ管理の必要性はなく、また、Pointer Arithmeticも必要ない。

GoでのPointerの操作

address Operator

&はaddress operatorであり、指定した変数の値が格納されているメモリ上の場所を返す。

x := 10
pointerToX := &x
fmt.Println(pointerToX) // 0xc0000160d8

indirect operator

*はindirection operatorであり、pointerを受け取ってpointerが示す値を返す。

fmt.Println(*pointerToX) // 10

indirection operatorはnil pointerに対して適用するとpanicが起きるので、必ず事前にnilであるか判定を行う必要がある。

	var z *int
	fmt.Println(z == nil)
	fmt.Println(*z) // panic: runtime error: invalid memory address or nil pointer dereference

pointer type
typeの前に*をつけるとpointer typeが定義できる

	x2 := 15
	var pointerToX2 *int
	pointerToX2 = &x2

注意事項

Primitive Type(numbers, booleans, strings)はメモリーアドレスを持っておらず、&が使えない。
必要な場合には事前に変数を定義してからPointerでそれを指し示すようにする。

You can’t use an & before a primitive literal (numbers, booleans, and strings) or a constant because they don’t have memory addresses; they exist only at compile time.

Structでフィールドの値がPrimitive typeに対するPointerの場合、リテラルをフィールドにアサインできない。

type person struct {
    FirstName  string
    MiddleName *string
    LastName   string
}

p := person{
  FirstName:  "Pat",
  MiddleName: "Perry", // This line won't compile
  LastName:   "Peterson",
}

Goの環境構築

Golang

インストールし直すときとかも基本的にGitHubから直接とってこないと最新バージョンのGoがインストールできないっぽい。

git clone https://github.com/syndbg/goenv.git ~/.goenv
% goenv -v
goenv 2.0.0beta11

# インストール可能なバージョンの一覧を確認
% goenv install -l

net/httpの活用

最も簡単なWebサーバ

package main

import (
	"fmt"
	"log"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}
ログインするとコメントできます