Help us understand the problem. What is going on with this article?

Go初学者が学んだことまとめ〜その1〜(構成と実行、構文)

More than 1 year has passed since last update.

はじめに

下記の動画を見て、Go面白そうだなと思い勉強を始めました。
今Web業界で非常に流行っている言語、高速なAPIサーバとしてNode.jsからGoに入れ替わってきているという点で、私は興味を持ちました。
Go(Golang)のWeb業界における重要度や将来性に関して説明します。

そしてこの本を読みました。
スターティングGo言語

学んだことをこちらにまとめます。
ちなみに、私はNode.jsの代わりとなるかという視点でGoを見ているので、JavaScript,TypeScriptとの比較がたまに入っています。
また、ここでは、Goの基本について網羅的には書いていません。他の言語と違う特徴的なところを中心にピックアップしてまとめています。

Goを一言でいうと

Cの高速性とスクリプト言語の手軽さを両立させる言語

Goの用途

上記の動画で述べられていますが、高速なAPIサーバを実現する用途でよく使われているようです。
PHP、Ruby、Pythonなどのスクリプト言語は速度が遅く、それに比べてコンパイル言語であるGoはパフォーマンス面で優れています。PHP、Ruby、Pythonは高機能なWebフレームワークが存在しますが、APIサーバであれば特に大掛かりな機能は必要ないのでこれらの言語、フレームワークを選択する必要はありません。それであれば高速でシンプルな実装が可能なGoがいいのではないかという理由で選ばれるようです。
また、マイクロサービスでGoを選択するという用途が今の流行りのようです。

構成と実行

パッケージ

Goは、変数や関数といったプログラムの要素は、必ずパッケージに属する。
1つのファイルに記述できるのは単一のパッケージのみ。
1つのディレクトリには1つのパッケージ定義のみ。
パッケージはimportして使う。

main.go
package main

import (
    "fmt"
)

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

ビルドと実行

ビルドと実行を一つのコマンドでできる。

$ go run main.go

ビルドと実行を分けるとこうなる。

$ go build -o main main.go
$ ./main

構文

変数

型推論

TypeScriptと似ている。
下記はほぼ同等の意味。

Go
i := 1
TypeScript
let i = 1;

参照

参照されない変数があるとエラーになる。
Warningでは生温いという思想のようだ。

func main() {
    a := 0 //aは使われていないのでエラー
}

数値型

int8〜int64まである。
符号なしはuint。

i := 1 //int型。Goが32bit/64bit実装かで変わる。
i := int8(5) //明示的にbitを指定して初期化する場合
f := 1.0 //float64型。浮動小数点型はGoが32bitか64bitでは変わらない。

初期値

変数定義をすると型によって初期値が決まっている。

var i int // i == 0
var s string // s == ""

定数

JavaScript,TypeScriptのconstとは違う。
コンパイル時に値が決まっていないといけない。

const A = 1
const B = A + 1 // B == 2
a := 1
const C = a + 1 //コンパイルエラー。定数の演算のみ許可する。

列挙型

Goに列挙型はないが、iotaというの使うと列挙型に近い振る舞いを実現できる。

const(
    A = iota // A == 0
    B        // B == 1
    C        // C == 2
)

演算子

特別なところは特になし。

関数

エラー

Goには例外機構(try-catch-finally的なもの)がない。

この書き方がGoにおける一種のイディオムで頻出する表現。

result,err := doSomething()
if(err != nil) {
    //エラー処理
}

戻り値の省略表記

ローカル変数の初期化、関数の戻り値にする、という処理を省略して表記できる。

func doSomething() (x, y int) {
    y = 5
    return //x==0, y==5がreturnされる
}

変数として扱える

関数を変数として扱える。この辺りはJavaScriptでもお馴染み。

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

クロージャ

こんな感じのジェネレータを作ることができる。

Go
package main

import "fmt"

func integers() func() int {
    i := 0 //クロージャ内から参照されていると、関数の実行が完了しても破棄されない
    return func() int {
        i += 1
        return i
    }
}

func main() {
    ints := integers()
    fmt.Println(ints()) // => "1"
    fmt.Println(ints()) // => "2"
    fmt.Println(ints()) // => "3"
}



ちなみに、TypeScriptでも同じことができる。

TypeScript
function integers(): () => number {
    let i = 0;
    return () => {
        i += 1;
        return i;
    };
}

const ints = integers();
console.log(ints());  // => "1"
console.log(ints());  // => "2"
console.log(ints());  // => "3"

スコープ

識別子の1文字目が大文字の場合は他のパッケージから参照できて、小文字だと参照できない。
public/privateを大文字/小文字で区別するという斬新な仕様。

package foo

const (
    A = 1 //public
    b = 1 //private
)

var (
    m = 256 //private
    N = 512 //public
)

//public
func DoSomething() {    
}

//private
func doSomething() {    
}

制御構文

for

ループはforだけ。

for{
    //無限ループ
}
//よくあるfor文
for i := 0; i < 10; i++ {
}
//範囲節によるfor
fruits := [3]string{"Apple", "Banana", "Cherry"}
for i, s := range fruits {
    fmt.Printf("fruits[%d]=%s\n", i, s)
}
// fruits[0]=Apple
// fruits[1]=Banana
// fruits[2]=Cherry

if

簡易文付きifという文法がある。

if _, err := doSomething(); err != nil {
    //エラーの処理
    //errのスコープはif文の中だけ
}

switch文でも同様の文法がある。

defer

関数終了時に実行される式を登録できる。
いくらでも登録可能。後から登録された方から実行される。スタック的。
リソース解放処理でよく使われる。

func runDefer() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    fmt.Println("done")
}
runDefer()
// done
// 2
// 1
//という順番で出力される

panicとrecover

panicはランタイムパニックを発生させ、プログラムを中断させる。
panic時でもdeferは実行される。
recoverはランタイムパニックによるプログラムの中断を回復するための機能。
recoverはdeferと組み合わせて使う。

func runPanic() {
    defer func() {
        if x := recover(); x != nil {
            fmt.Println(x) //"error"が出力される
        }
    }()
    panic("error")
    fmt.Println("done") //これは実行されない
}
runPanic()
fmt.Println("Hello") //これは実行される

関数をまたぐgoto文(悪名高い文法)とも言えるので、よほどの場合を除いて使うべきではない。

init

パッケージの初期化を目的とした特殊な関数initを定義できる。
main関数の前に実行される。
importされた場合は、その時点で実行される。

import (
    "fmt"
)

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

func main() {
    fmt.Println("main")
}
//init
//main
//という順番で出力される

go

並列処理を実行させるもの。
deferと同様、関数呼び出しの形式で式を受け取る。
独立して動作する実行単位のことをゴルーチンという。

func sub() {
    for {
        fmt.Println("sub loop")
    }
}

func main() {
    go sub()
    for {
        fmt.Println("main loop")
    }
}
//sub loop
//main loop
//が出力され続ける

ちなみに、デフォルトではCPUを1つしか使わないので、複数のCPUを使いたい場合は、明示的に使用するCPUの数を指定する必要がある。

//使えるCPU全てを使う場合
runtime.GOMAXPROCS(runtime.NumCPU())

構文名が言語名と同じなので、おそらくこれが一番注目ポイントなのでは。

まとめ

特徴的なところを挙げてみました。
大文字/小文字でpublic/privateを区別する、ループがforのみ、goだけで並列処理ができてしまうなど、とにかくシンプルに、という思想なのがよくわかります。
その2に続きます。

参考

Go(Golang)のWeb業界における重要度や将来性に関して説明します。
スターティングGo言語

関連

Go初学者が学んだことまとめ〜その2〜(参照型)
Go初学者が学んだことまとめ〜その3〜(構造体、インターフェース)
Go初学者が学んだことまとめ〜その4〜(コマンド、パッケージ)

haru_iwamoto
スマホアプリのフロントエンド、バックエンドの開発をしているエンジニア。 フルスタックエンジニアと名乗りたいけど名乗れない。 Rails、TypeScript、Unityなど。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away