Go言語初心者が失敗しがち/間違えがちなこと5選

Go言語 Golang

シンプルで分かりやすい言語仕様が特徴の、Go言語(Golang)。

軽快な実行速度や書きやすい並列処理、さまざまなOSに対応したクロスコンパイルなど、魅力的な機能を多く揃えている近年注目のプログラミング言語です。

Go言語は他の静的型付き言語と比べて学習コストが低く、いくつかの間違いやすい点を抑えれば扱いやすいでしょう。

今回はGo言語の失敗しがち/間違えがちなことを5つピックアップしました。

1. 変数名と型の宣言で、順序を間違えがち

Java、C++、C#などの代表的な静的型付き言語では、型と変数の宣言は「型 + 変数」という順序で行われることが多いです。

一方のGo言語は、「変数 + 型」という順序で宣言を行います。

var string str  // 間違い(エラー:undefined: str)

var str string // 正しい変数宣言

func output(string str) {  // 間違い(エラー:undefined: str)
	fmt.Println(str)
}

func output(str string) {  // 正しい関数宣言
	fmt.Println(str)
}

上記のように、変数名と型の宣言で順序を「型 + 変数」にしてしまうと、Go言語のコンパイラが型名を読み取れないためエラーが出力されます。

単なる変数宣言だけではなく、関数の引数として宣言する変数も「変数 + 型」が正しい順序となります。

2. stringから「文字」を取得しようとして、「1バイト目」を取得しがち

一般的なプログラミング言語と同様に、Go言語の文字列の型はstringです。

しかし文字列とはいうものの、Go言語の文字列は実質「バイト配列」としてできています。

str1 := "文字列"
fmt.Println(str1[0])  // 出力:230 ( stringの中身はバイト配列であり、1バイト目の値が出力される )

このように文字列から配列の0番目を取得しようとすると、1文字目の文字ではなく1バイト目の値を取得してしまうのです。

バイト値を文字列に戻すにはstring型に変換する必要がありますが、扱う文字列が日本語などマルチバイト文字の場合、以下のような文字化けが発生します。

fmt.Println(string(str1[0])) // 出力:æ ( 日本語は1文字3バイトのため、1バイト目だけを文字列として読み取ると文字化けする )

これではいちいち文字を抜き出すときに不便ですよね。

こんなときに登場するのが「rune型」です。rune型はUnicodeのコードポイントごとに文字を保持する型です。

stringの文字列をrune型の配列にすれば、文字のバイト数に関わらず、1文字ずつで区切られたデータを取得できます。

str1 := "文字列"
r1 := []rune(str1)  // rune型の配列 ( 1文字ずつ文字列を保持 )
fmt.Println(r1[0])  // 出力:25991 ( 1文字目のUnicodeのコードポイント )
fmt.Println(string(r1[0])) // 出力:文 ( rune型はUnicodeのコードポイントなので、string型に戻すと正しく1文字を表示できる )

「絶対にマルチバイト文字を使わない!」という場面ならば、rune型を使わずとも文字の抜き出し等が可能です。

しかし少しでもマルチバイト文字が含まれる可能性がある場合には、rune型を使うのが良いでしょう。

3. 未使用のimportや変数が原因で、コンパイルエラーを出してしまいがち

Go言語では、使用していないimportや変数は、コンパイルエラーになります。

これはクリーンなコードを書くための、Go言語特有の仕様です。

func foo() {
	str := "文字列" // エラー:str declared and not used
	fmt.Println("message")
}

上記のように、明らかに使っていない変数がある場合、その変数を削除すればエラーが出なくなります。

では以下のように「メソッドの戻り値が2つ以上ある場合」はどうすればいいでしょうか?

plusAndMinus := func(num1 int, num2 int) (int, int) {
	return num1 + num2, num1 - num2
}
result1, result2 := plusAndMinus(8, 7)  // result1が不使用のためエラー:result1 declared and not used
fmt.Println(result2)

戻り値が2つあるため、それを受け取る変数も2つ定義しています。しかしこのうち片方のみしか使わない場合、未使用の変数としてコンパイルエラーが出てしまいます(上記コードでは変数result1が不使用のためエラーが出ています)。

こんなとき役立つのが変数名「_(アンダースコア)」です。「_」で定義された変数は、不使用の変数として扱われます。

先ほどのコードの変数result1を「_」に変えてみましょう。

plusAndMinus := func(num1 int, num2 int) (int, int) {
	return num1 + num2, num1 - num2
}
_, result2 := plusAndMinus(8, 7) // エラーが出ない (変数「_ ( アンダースコア ) 」は使わない変数として扱われる
fmt.Println(result2)

これでエラーが出なくなりました。変数「_」は、使われなくても問題ない扱いになるのですね。

戻り値の関係で使用しない変数が生まれてしまう場合、変数名を「_」にしてコンパイルエラーを回避しましょう。

4. 配列の宣言でサイズに変数を指定して、エラーを出してしまいがち

Go言語では、次のように配列を宣言&初期化します。

var arr1 [3]string // 配列の宣言
arr1[0] = "foo"
arr1[1] = "hoo"
arr1[2] = "hoge"
fmt.Println(arr1)  // 出力:[foo hoo hoge]

arr2 := [3]string{"a", "b"} // 配列の宣言と初期化
fmt.Println(arr2)  // 出力:[a b ]

arr3 := [...]string{"1", "2", "3", "4"} // 配列の宣言と初期化(略式)
fmt.Println(arr3)  // 出力:[1 2 3 4]

このとき「サイズを実行時の変数の値に応じて動的に変えたい」という場合、以下のように書くとどうなるでしょうか?

// 何らかの数値を取得
somethingNum := getRandomNum()

// ▼ 取得した数値のサイズの配列を作成しようとすると、、、
var arr [somethingNum]string // non-constant array bound somethingNum

コンパイルエラーが出てしまいましたね。Go言語では、配列のサイズには定数しか指定できないのです。

Go言語で配列のサイズを動的に指定したい場合には、配列ではなくsliceを使いましょう。

sliceとは、arrayへの参照をもつデータ構造。いわゆる可変長配列みたいなものです。固定長配列のビューとして実装されています。

sliceは以下のように作成します。可変長配列みたいなものなので、sliceを作成した後にappend()で自由に要素を追加できます。

またmake()で、最初にサイズを変数で指定することも可能です。

// 通常のslice作成(要素はあとでappend()で追加する)
var s1 []string
fmt.Println(s1)  // []

// 値の初期化を同時に行うslice作成
s2 := []string{"foo", "hoo", "hoge"}
fmt.Println(s2)  // [foo hoo hoge]

// make()でサイズを指定したsliceを作成する
somethingNum := getRandomNum() // 何らかの数値を取得
s3 := make([]string, somethingNum)
for i := 0; i < somethingNum; i++ {
	s3[i] = "index:" + strconv.Itoa(i)
}
fmt.Println(s3)  // [index:0 index:1 index:2 index:3 index:4]

さらに以下のように、スライス演算子([ 開始index : 終了index ] の形式で書く演算子)を使うことで、配列から配列の一部分をビューとして切り抜き、sliceを取得することも可能です。

// 配列
arr := [...]string{"foo", "hoo", "hoge", "fuge"}

// 配列の前2つを抜き出したslice
fmt.Println(arr[:2]) // [foo hoo]

// 配列の後2つを抜き出したslice
fmt.Println(arr[2:]) // [hoge fuge]

// 配列の中2つを抜き出したslice
fmt.Println(arr[1:3]) // [hoo hoge]

5. sliceに要素を追加するappend()の使い方を間違えがち

Go言語でsliceに要素を追加する場合には、append()を使います。

このappend()ですが、他の言語の可変長配列の要素追加系メソッドとは、やや特徴的な違いがあります。

多くの言語において、要素追加系メソッドは元の可変長配列を変更しますが、Go言語のappend()は戻り値のsliceのみに新しい要素が追加されます。つまり元のsliceを変更しているのではなく、新しいsliceを生成しているのです。

slice := []string{"foo", "hoo"}

// // ▼「append()を実行しているが、結果を使用されていない」というエラーがでる
// append(slice, "hoge") // エラー:append(slice, "hoge") evaluated but not used
// fmt.Println(slice)

// append()は戻り値をひろって使う ( 元のsliceを変更しない )
slice = append(slice, "hoge")
fmt.Println(slice) // [foo hoo hoge]

「append()は元のsliceを変更するものだ」と思い込んでいると、思わぬエラーにつまづきます。

append()を使うときは、戻り値を利用するようにしましょう。

まとめ

言語仕様がシンプルなGo言語は、学習コストが低く親切な言語です。

rune型やsliceなど、Go言語特有の言語仕様さえおさえれば、短期間で扱えるようになるでしょう。

間違えやすい基本的な箇所を抑えて、Go言語でアプリケーションやシステムを構築してみてください!

 

執筆:sig_Left
編集:内田一良(じきるう)
監修:庄子肇

 

SHARE

  • 広告主募集
  • ライター・編集者募集
  • WorkshipSPACE
エンジニア副業案件
Workship