目的
- GO言語のキャッチアップのためメモ書き
- 公式が英語なので、日本語化+注釈も入れながら進める(翻訳はDeepl)
はじめに
参考にしているサイトはGO公式に記載されているWiki Tutorialです。
本記事では上記URLのGetting Started
, Data Structures
まで扱います
チュートリアルやらずにこのページをざっと見る程度でもいいと思います
内容
まずはディレクトリを作成
$ mkdir gowiki
$ cd gowiki
wiki.goという名前のファイルを作成し、お気に入りのエディタで開き、以下の行を追加してください。
package main
import (
"fmt"
"io/ioutil"
)
つまりフォーマットI/Oでファイルに文字を入力してアップするには2つのパッケージが必要ということですね、なるほど!
次にデータ構造を決めるために定義を行います
データ構造を定義することから始めましょう。wikiは一連の相互接続されたページから構成され、それぞれがタイトルと本文(ページの内容)を持っています。ここでは、タイトルと本文を表す2つのフィールドを持つ構造体としてPageを定義します。
ファイル名wiki
だけあって、タイトル+内容=1つのページという意図がありますね、理解しやすいです。
type Page struct {
Title string
Body []byte
}
type []byteは「バイトのスライス」を意味します。Body要素は文字列ではなく[]バイトです。これは、後述するように、使用するioライブラリで期待される型だからです。
上で2つのパッケージをインポートした理由と繋がりますね
Page構造体は、ページデータがどのようにメモリに格納されるかを記述しています。しかし、永続的なストレージについてはどうでしょうか?Page上にsaveメソッドを作成することでそれに対処できます。
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
このメソッドのシグネチャは次のようになります。"これは save という名前のメソッドで、受信機として Page へのポインタ p を受け取ります。パラメータを取らず,エラー型の値を返します."
ここで動的型付け言語のみの方は Page へのポインタ p を受け取ります
の一文で ?
てなると思います(私がそうでした)
調べたら解説しているQiita記事がありました
ポインタ変数とはメモリ上のアドレスを値として入れられる変数のこと
とあるので、今回の例でいうと
- save()メソッドで作られた情報のアドレスはpに入ってる
- 現時点でpに入っている情報はタイトル:p.Title, 内容:p.body
ということですね!
それともう一点
パラメータを取らず,エラー型の値を返します
saveに成功したらエラーが返らないのでは?と思われる方もいると思います。
それについては説明があります。
saveメソッドはエラー値を返しますが、これはWriteFile(ファイルにバイトスライスを書き込む標準ライブラリ関数)の戻り値の型だからです。saveメソッドはエラー値を返し、ファイルの書き込み中に何か問題が起こった場合にアプリケーションが処理できるようにしています。すべてがうまくいけば、Page.save()はnilを返します(ポインタやインターフェイス、その他いくつかの型のためのゼロ値)。
saveが成功したらerrorではなくnilが返るという仕組みです。
続いてページのロード機能です。
ページを保存することに加えて、ページをロードしたいと思うでしょう。
func loadPage(title string) *Page {
filename := title + ".txt"
body, _ := ioutil.ReadFile(filename)
return &Page{Title: title, Body: body}
}
関数 loadPage は title パラメータからファイル名を構築し、ファイルの内容を新しい変数 body に読み込み、適切な title と body の値で構築された Page リテラルへのポインタを返します。
ここはポインタの仕組みを理解していればすんなり理解できますね。
与えられたtitleパラメーターで検索かけて合致したデータを持ってきているだけです。
ただしここ
body, _ := ioutil.ReadFile(filename)
ん? _って何?
一応説明文もあるので読みましょう。
関数は複数の値を返すことができます。標準ライブラリ関数io.ReadFileは[]byteとerrorを返します。loadPageでは、エラーはまだ処理されていません。アンダースコア(_)シンボルで表される「空白の識別子」は、エラーの戻り値を捨てるために使用されます(本質的には、値を何にも代入しません)。
つまり返却されるエラー値を捨てるために_をつけている
というかいらないなら返却しないように作ればいいのでは?
これについてはアンダースコア変数についてで同じ内容で質問されている方がいます。
GO言語の性質上、宣言した変数は使用しないとシンタックスエラーになってしまうので、確実にエラーにならないものはエラーを省略したいという意図があるそうです。
しかし確実にエラーがないなんて保証はあるのでしょうか。
もしエラーが起きた場合は?
そもそもその判断を人がやる時点で微妙ですよね。
そう思われた方も多いと思います。
安心してください。
それについて説明があります。
しかし、ReadFileがエラーに遭遇した場合はどうなるのでしょうか?例えば、ファイルが存在しないかもしれません。このようなエラーを無視してはいけません。ここでは、Pageとエラーを返すように関数を修正してみましょう。
上のソースを修正する流れになります。
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}
この関数の呼び出し元は、2 番目のパラメータをチェックすることができるようになりました。そうでない場合は、呼び出し側で処理できるエラーとなります
if文でエラーチェックを行い、戻り値にnilを追加する事で両方の問題を解決しています。
これでソースの追記は終わりでチェックに入ります。
この時点で、シンプルなデータ構造と、ファイルへの保存やファイルからの読み込みが可能になりました。書いたものをテストするためにメイン関数を書いてみましょう。
このようにコンパイルして実行することができます。
$ go build wiki.go
$ ./wiki
This is a sample Page.
実行してThis is a sample Page.
と表示されれば成功です
全文(コピペでOK)
package main
import (
"fmt"
"io/ioutil"
)
type Page struct {
Title string
Body []byte
}
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}
func main() {
p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
p1.save()
p2, _ := loadPage("TestPage")
fmt.Println(string(p2.Body))
}
終わりに
メモ書き程度に投稿したので走り書きみたいな内容になっています。
何かあればコメントしてください!