- 投稿日:2020-09-18T21:56:07+09:00
A Tour of Go メモ 【7】五日目
A Tour of GO
interface
インターフェース型と同じ関数を実装していない物はインターフェース型に代入できない
func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} // MyFloatにはAbser()が実装されている a = f // *VertexにもAbser()は実装されている a = &v // aは*Vertexではないため、Aber()が実装されていない a = v fmt.Println(a.Abs()) } type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } // *vertexにはAbserと同じAbs()関数が実装されている // Vertexのポインタ型に対して、実装している func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }メソッドを実装し、インターフェースを実装する
type I interface { M() } type T struct { S string } // T に I(インターフェース)と同じメソッドを実装する func (t T) M() { fmt.Println(t.S) } func main() { //TはIと同じメソッドを持っているので、I型と判断されるため、i(I型)に代入できる var i I = T{"hello"} i.M() }Interface Values
type I interface { M() } type T struct { S string } func (t *T) M() { fmt.Println(t.S) } type F float64 func (f F) M() { fmt.Println(f) } func main() { var i I i = &T{"Hello"} describe(i) i.M() // (&{Hello}, *main.T) Hello //M() は元となるT型に宣言した同名のメソッドが呼び出される i = F(math.Pi) describe(i) i.M() //(3.141592653589793, main.F) 3.141592653589793 //M() は元となるT型に宣言した同名のメソッドが呼び出される }Interface values with nil underlying values
type I interface { M() } type T struct { S string } func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) } func main() { // この時点ではiはnilである var i I // Tには何も入っていない,nilである var t *T // しかし、 iにはnilであるT型が入っているので、i自体は非nilである i = t describe(i) // (<nil>, *main.T) <nil> i.M() i = &T{"hello"} describe(i) // (&{hello}, *main.T) <nil> i.M() } func describe(i I) { fmt.Printf("(%v, %T)\n", i, i) }Nil interface values
type I interface { M() } func main() { //値も具体的な型も持っていない var i I describe(i) i.M() // 何も値も持たないインターフェース型からはメソッドを呼べない } func describe(i I) { fmt.Printf("(%v, %T)\n", i, i) }The empty interface
空のインターフェースにはどんな型の値も扱うことができる
func main() { var i interface{} describe(i) i = 42 describe(i) i = "hello" describe(i) } func describe(i interface{}) { fmt.Printf("(%v, %T)\n", i, i) }Type assertions
string型をインターフェース型に変換した場合、
インターフェース型の中身が"hello"だったとしても、string型として扱うことができない。
そのため、再度インターフェース型からstring型に戻す作業が必要になるfunc main() { var i interface{} = "hello" s := i.(string) fmt.Println(s) // okがiの中身がstring型値を保持しているかをboolで返す s, ok := i.(string) fmt.Println(s, ok) f, ok := i.(float64) fmt.Println(f, ok) f = i.(float64) // panic fmt.Println(f) }Stringers
fmtパッケージのソース
Printlnメソッドが実行された時、引数がStringer型だったらstring()が実行されるはず
574行目あたりだろうか・・・type Person struct { Name string Age int } //fmtのパッケージに以下の Stringer インターフェースの記述がある ************************************************************ type Stringer interface { String() string } ************************************************************ //Stringer型と同じString()メソッドを宣言すると Person型にStringerインターフェースを実装できる func (p Person) String() string { return fmt.Sprintf("%v (%v years)", p.Name, p.Age) } func main() { a := Person{"Arthur Dent", 42} z := Person{"Zaphod Beeblebrox", 9001} fmt.Println(a, z) }Errors
組み込みのerrorインターフェースを実装し、エラーメッセージをカスタマイズする
type MyError struct { When time.Time What string } // error型のインターフェースを実装 func (e *MyError) Error() string { return fmt.Sprintf("at %v, %s", e.When, e.What) } //実行すると現在時刻と文字列が入った構造体を返す func run() error { return &MyError{ time.Now(), "it didn't work", } } func main() { // もし、run()で戻り値があったら、戻り値を出力 if err := run(); err != nil { fmt.Println(err) } }Readers
func main() { r := strings.NewReader("Hello, Reader!") b := make([]byte, 8) for { // Readメソッドはint型とerror型の戻り値がある n, err := r.Read(b) fmt.Printf("n = %v err = %v b = %v\n", n, err, b) fmt.Printf("b[:n] = %q\n", b[:n]) //ストリームが終わったら、Readメソッドが返すio.EOFというエラーがerrに入るので、 //それを判定して、ループを抜ける if err == io.EOF { break } } }Images
import ( "fmt" "image" ) ***************************************************************** *imageパッケージの中身 type Image interface { ColorModel returns the Image's color model. ColorModel() color.Model //Bounds returns the domain for which At can return non-zero color. // The bounds do not necessarily contain the point (0, 0). Bounds() Rectangle // At returns the color of the pixel at (x, y). // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid. // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one. At(x, y int) color.Color } type RGBA struct { // Pix holds the image's pixels, in R, G, B, A order. The pixel at // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. Pix []uint8 // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle } //RGBAにはAt, Boundes ColorModel()などのメソッドがあるので、Imageインターフェースが実装されている func (p *RGBA) At(x, y int) color.Color func (p *RGBA) ColorModel() color.Model func (p *RGBA) Bounds() Rectangle func Rect(x0, y0, x1, y1 int) Rectangle //Rect is shorthand for Rectangle{Pt(x0, y0), Pt(x1, y1)}. func NewRGBA(r Rectangle) *RGBA //NewRGBA returns a new RGBA image with the given bounds. *colorのパッケージ type Color interface { // RGBA returns the alpha-premultiplied red, green, blue and alpha values // for the color. Each value ranges within [0, 0xffff], but is represented // by a uint32 so that multiplying by a blend factor up to 0xffff will not // overflow. // // An alpha-premultiplied color component c has been scaled by alpha (a), // so has valid values 0 <= c <= a. RGBA() (r, g, b, a uint32) } ***************************************************************** func main() { // Rect()でRectange型の構造体を作成 // Rectange型を引数として、RGBA型の構造体を作成 m := image.NewRGBA(image.Rect(0, 0, 100, 100)) fmt.Println(m.Bounds()) fmt.Println(m.At(0, 0).RGBA()) }
- 投稿日:2020-09-18T12:38:39+09:00
GOPATHについてメモ
Goの環境構築をしていてGOPATHについて結理解するのに時間がかかったのでまとめ
他の言語と全然違って戸惑い?GOPATHとは
GOPATH
は、ワークスペースのルートのこと
ソースコードとかパッケージとかコンパイルした後の実行ファイルを全部ぶちこむ?デフォルトは
$HOME/go
で、Goのインストールディレクトリじゃなければどこでもいい。※みた感じデフォルトのまま使っている人が多そう。
フォルダ構成
例えばGOPATHを
$HOME/go
にしたときのフォルダ構成はこんな感じgo ├── src ├── pkg └── binsrc
ソースコードを置くディレクトリ。一ディレクトリ一アプリケーションが基本。
packageがmainであれば実行可能アプリケーション、違ったらアプリケーションパッケージpkg
アプリケーションパッケージが保存される
bin
実行ファイルが保存される。
- 投稿日:2020-09-18T12:11:43+09:00
ゴルーチンでWaitGroupを使う
はじめに
ゴルーチンを使った処理の際に登場するWaitGroupについて勉強したので記しておきます。
コード
WaitGroupを使わない場合
まずはWaitGroupを使用しない場合です。
f1( )もf2( )も非常に速く実行されるため、関数が独立して実行されているかどうかで違いがほとんど出ません。
そのため、timeパッケージの関数Sleepを使って両方の関数に遅延を追加しています。package main import ( "fmt" "time" ) func f1() { for i := 0; i < 10; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func f2() { for i := 100; i < 110; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func main() { go f1() go f2() }これを実行してみてください。
コードはこちら→ https://play.golang.org/p/8z21HqgIB21恐らく何も表示されないと思います。
これはf1( )とf2( )が数字を出力する前にmain関数が終了してしまったからです。
実行結果をきちんと見るためにはmain関数の最後に遅延を追加する必要があります。遅延を追加したコードは以下になります。
package main import ( "fmt" "time" ) func f1() { for i := 0; i < 10; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func f2() { for i := 100; i < 110; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func main() { go f1() go f2() time.Sleep(100 * time.Millisecond) }これを実行してみると(コード→https://play.golang.org/p/LmovZBNCSFm) 、
以下のようにきちんと出力されています。100 0 101 1 102 2 103 3 4 104 105 5 6 106 7 107 8 108 109 9WaitGroupを使う場合
次はWaitGroupを使用する場合を見てみましょう。
WaitGroupを使用しない場合、関数Sleepを使ってプログラムの終了を遅延させ、ゴルーチンが処理を完了する前にプログラムを終了させないようにしました。WaitGroupはsyncパッケージに含まれており、次の処理を開始する前にすべてのゴルーチンが処理を完了しているかどうか確認する場面に対応する仕組みです。
使い方は以下の通り。
- WaitGroupを宣言する
- Add()を使ってWaitGroupのカウンタを初期化する
- ゴルーチンが作業を完了するとDone()を使ってカウントを減らす
- Wait()でカウンタが0になるまで実行を中断する
では、実際に先ほどのコードをWaitGroupを使って書いてみます。
package main import ( "fmt" "sync" "time" ) func f1(wg *sync.WaitGroup) { for i := 0; i < 10; i++ { time.Sleep(1 * time.Millisecond) fmt.Printf("%d ", i) } wg.Done() } func f2(wg *sync.WaitGroup) { for i := 100; i < 110; i++ { time.Sleep(1 * time.Millisecond) fmt.Printf("%d ", i) } wg.Done() } func main() { var wg sync.WaitGroup wg.Add(2) go f1(&wg) go f2(&wg) wg.Wait() }実行結果は以下のようになります。
コード→https://play.golang.org/p/2ODVxIi5aXm100 0 1 101 102 2 3 103 4 104 105 5 6 106 107 7 108 8 9 109しっかり出力されています。
どうしてそうなるのでしょうか。まず、WaitGroupの変数wgを定義します。
次にAddを呼び出し、引数に2を渡します。
そして、f1( )、f2( )が呼び出されると、それぞれカウンタが1少なくなります。
Wait()を呼び出しているので、Done()が2回呼び出されカウンタが0になるまで実行は中断されます。仮に、Done()を呼び出さず、カウンタが0にならなかった場合はエラーとなります。
0 100 101 1 102 2 103 3 4 104 105 5 106 6 7 107 108 8 9 109 fatal error: all goroutines are asleep - deadlock!最後に
今までWaitGroupって何?という状態でしたが、何をしているのか理解することができました。
少しでも参考になれば幸いです!
- 投稿日:2020-09-18T12:11:43+09:00
[Go]ゴルーチンでWaitGroupを使う
はじめに
ゴルーチンを使った処理の際に登場するWaitGroupについて勉強したので記しておきます。
コード
WaitGroupを使わない場合
まずはWaitGroupを使用しない場合です。
f1( )もf2( )も非常に速く実行されるため、関数が独立して実行されているかどうかで違いがほとんど出ません。
そのため、timeパッケージの関数Sleepを使って両方の関数に遅延を追加しています。package main import ( "fmt" "time" ) func f1() { for i := 0; i < 10; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func f2() { for i := 100; i < 110; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func main() { go f1() go f2() }これを実行してみてください。
コードはこちら→ https://play.golang.org/p/8z21HqgIB21恐らく何も表示されないと思います。
これはf1( )とf2( )が数字を出力する前にmain関数が終了してしまったからです。
実行結果をきちんと見るためにはmain関数の最後に遅延を追加する必要があります。遅延を追加したコードは以下になります。
package main import ( "fmt" "time" ) func f1() { for i := 0; i < 10; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func f2() { for i := 100; i < 110; i++ { time.Sleep(1 * time.Microsecond) fmt.Printf("%d ", i) } } func main() { go f1() go f2() time.Sleep(100 * time.Millisecond) }これを実行してみると(コード→https://play.golang.org/p/LmovZBNCSFm) 、
以下のようにきちんと出力されています。100 0 101 1 102 2 103 3 4 104 105 5 6 106 7 107 8 108 109 9WaitGroupを使う場合
次はWaitGroupを使用する場合を見てみましょう。
WaitGroupを使用しない場合、関数Sleepを使ってプログラムの終了を遅延させ、ゴルーチンが処理を完了する前にプログラムを終了させないようにしました。WaitGroupはsyncパッケージに含まれており、次の処理を開始する前にすべてのゴルーチンが処理を完了しているかどうか確認する場面に対応する仕組みです。
使い方は以下の通り。
- WaitGroupを宣言する
- Add()を使ってWaitGroupのカウンタを初期化する
- ゴルーチンが作業を完了するとDone()を使ってカウントを減らす
- Wait()でカウンタが0になるまで実行を中断する
では、実際に先ほどのコードをWaitGroupを使って書いてみます。
package main import ( "fmt" "sync" "time" ) func f1(wg *sync.WaitGroup) { for i := 0; i < 10; i++ { time.Sleep(1 * time.Millisecond) fmt.Printf("%d ", i) } wg.Done() } func f2(wg *sync.WaitGroup) { for i := 100; i < 110; i++ { time.Sleep(1 * time.Millisecond) fmt.Printf("%d ", i) } wg.Done() } func main() { var wg sync.WaitGroup wg.Add(2) go f1(&wg) go f2(&wg) wg.Wait() }実行結果は以下のようになります。
コード→https://play.golang.org/p/2ODVxIi5aXm100 0 1 101 102 2 3 103 4 104 105 5 6 106 107 7 108 8 9 109しっかり出力されています。
どうしてそうなるのでしょうか。まず、WaitGroupの変数wgを定義します。
次にAddを呼び出し、引数に2を渡します。
そして、f1( )、f2( )が呼び出されると、それぞれカウンタが1少なくなります。
Wait()を呼び出しているので、Done()が2回呼び出されカウンタが0になるまで実行は中断されます。仮に、Done()を呼び出さず、カウンタが0にならなかった場合はエラーとなります。
0 100 101 1 102 2 103 3 4 104 105 5 106 6 7 107 108 8 9 109 fatal error: all goroutines are asleep - deadlock!最後に
今までWaitGroupって何?という状態でしたが、何をしているのか理解することができました。
少しでも参考になれば幸いです!
- 投稿日:2020-09-18T11:15:54+09:00
Slash Commandsで自分が使用している端末のグローバルIPは取得できるのか【API Gateway + Lambda(Go)】
はじめに
こんにちは。先月AWS ChatbotからRDSを操作していて、とても簡単にSlackから実行できて感動したので、第2弾です!今回はSlackから自分のグローバルIPを取得できるのかを、試してみました!
結論
残念ながら、Slackから自分のグローバルIPは取得できませんでした。
Slackで自分のグローバルIPを取得するべく、Slash CommandsというSlackのapiと、AWSのAPI Gateway、Lambdaを使って試みたので、その過程について備忘録です。
方法検討
自分のグローバルIPを取得して、それを引数としてLambdaを実行したかったので、AWSを使う方針で進めていきます。
調査したところ、SlackからLambdaを実行する方法を2つ見つけました。
- API Gatewayを使う方法(Slash Commandsの作成)
- AWS Chatbotを使う方法
API Gatewayはアクセス元のグローバルIPを取得できるというのを知ったので、今回はAPI Gatewayを使う方法(Slash Commandsの作成)で実装することにしました!
構成図
構成は下図の通りです。
ちなみに、Slash commandsは3000ms以内に応答を返す必要があるので、処理時間が長いものに関しては先にSlackに応答を返す必要があるみたいです。今回はアクセス元IPを返すだけなので、こちらの構成で実装しました。
参考:https://api.slack.com/interactivity/slash-commandsやってみる
Lambda関数の作成
今回はチャンネルを識別し、指定チャンネルから実行されていれば、アクセス元のグローバルIPを返すというLambda関数を作りました。
チャンネルを識別する意図としては、Slash Commandsは、ワークスペース内の全チャンネル、全DMで使えるもので、チャンネルを指定することができません。今回作成するSlash CommandsはグローバルIPを返すだけなので、どこで実行できても問題はないのですが、実際には指定したプライベートチャンネルのメンバーのみしか実行できないようにしたかったので、実行チャンネルを識別し、実行権限があるのかを判断する処理を入れています。
IAMロール
特別必要な権限はないので、基本的なLambdaアクセス権限をアタッチしたロールを使用します。
Slackから送られるデータと構造
Slackから送られるデータと構造は下記の通りです。
※ Valueは削除しています。本来はそれぞれ値が入っています。
※ 後述のAPI Gatewayのマッピングテンプレートを使用した場合の構造です。{ "querystring":{ "api_app_id":"", "channel_id":"", "channel_name":"", "command":"", "response_url":"", "team_domain":"", "team_id":"", "text":"", "token":"", "trigger_id":"", "user_id":"", "user_name":"" }, "source_ip":"" }ソースコード
package main import ( "github.com/aws/aws-lambda-go/lambda" "os" ) type MyEvent struct { Querystring Querystring `json:"querystring"` SourceIp string `json:"source_ip"` } type Querystring struct { ChannelId string `json:"channel_id"` } /************************** 処理実行 **************************/ func run(event MyEvent) (interface{}, error) { // os.Getenv()でLambdaの環境変数を取得 if event.Querystring.ChannelId == os.Getenv("channelId") { return event.SourceIp, nil } res := "実行権限がありません。" return res, nil } /************************** メイン **************************/ func main() { lambda.Start(run) }API Gatewayの作成
API Gatewayの作成を行います。
POSTメソッドを作成し、先ほど作成したLambda関数を紐付けます。そして、マッピングテンプレートの設定をしていきます。
[統合リクエスト]をクリックします。最下部にあるマッピングテンプレートを開き、リクエスト本文のパススルーはテンプレートが定義されていない場合 (推奨)を選択します。そして、[マッピングテンプレートの追加]をクリックします。
[マッピングテンプレートの追加]を押したら出てくるテキストボックスに
application/x-www-form-urlencoded
を入力、保存します。テンプレートの入力フォームが出てくるので、下記のマッピングテンプレートを貼り付け、[保存]をクリックします。
【マッピングテンプレート】
#set($rawAPIData = $input.path('$')) ## escape any quotes #set($rawAPIData = $rawAPIData.replace('"', '\"')) ## first we get the number of "&" in the string, this tells us if there is more than one key value pair #set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length()) ## if there are no "&" at all then we have only one key value pair. ## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs. ## the "empty" kv pair to the right of the ampersand will be ignored anyway. #if ($countAmpersands == 0) #set($rawPostData = $rawAPIData + "&") #end ## now we tokenise using the ampersand(s) #set($tokenisedAmpersand = $rawAPIData.split("&")) ## we set up a variable to hold the valid key value pairs #set($tokenisedEquals = []) ## now we set up a loop to find the valid key value pairs, which must contain only one "=" #foreach( $kvPair in $tokenisedAmpersand ) #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length()) #if ($countEquals == 1) #set($kvTokenised = $kvPair.split("=")) #if ($kvTokenised[0].length() > 0) ## we found a valid key value pair. add it to the list. #set($devNull = $tokenisedEquals.add($kvPair)) #end #end #end { "querystring" : { #foreach( $kvPair in $tokenisedEquals ) ## finally we output the JSON for this pair and append a comma if this isn't the last pair #set($kvTokenised = $kvPair.split("=")) #if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0) #set($kvValue = $kvTokenised[1]) #else #set($kvValue = "") #end #if( $foreach.hasNext ) #set($itemDelimiter = ",") #else #set($itemDelimiter = "") #end "$kvTokenised[0]" : "$kvValue"$itemDelimiter #end }, "source_ip" : "$context.identity.sourceIp" }最後にリソースのデプロイを行い、発行されたURLをコピーしておきます。
Slash Commandsの作成
Slack appの作成
Slack apiからSlack appを作成していきます。
[Create New App]をクリックするとAppの作成画面に遷移します。
ここで、Slack Appの名前とAppを実行したいワークスペースを選択します。
そして、[Create App]をクリックするとSlack Appが完成します。Slash commandsの作成
Appの設定ページに移動して、Slash commandsを作成します。
左側のメニューから[Slash commands]を選択し、[Create New Commands]をクリックします。
次に、Slackから送信するコマンド名、前述のAPI Gatewayで取得したリクエストURL、説明文の3つを入力します。今回は、
/get_ip
というコマンド名を設定しました。今回は引数を入力しないのでヒントは入れませんでしたが、引数など入力時の補足がある場合はUsage Hintに入力しておきます。Escape channels, users, and links sent to your appにチェックを入れることで、引数にユーザ名を入れる時に、ユーザ名だけではなくユニークなユーザIDも送ることができ、アカウントを識別できるそうです。今回はアカウントの識別は不要なので、チェックは入れませんでした。
それぞれ入力完了後、[Save]をクリックすると、Slash commandsが完成します。
Botの名前設定
このままAppのインストールに進むと、(App名)にはインストールするボットユーザーがありません。というエラーが出て、Appをインストールできなかったので、Appの設定ページから、Botを作成します。
ちなみに、App名が小文字英数字の場合は、自動でBotが設定されるみたいので、初期値が入っていればこのフローは不要です。わたしはIP取得マンというApp名にしていたためボットユーザが作られなかったのだと思われます。
左側のメニューから[App Home]を選択し、Your App's Presence in Slackの[Edit]をクリックします。
Botの表示名とユーザ名を入力して[Add]をクリックすると、Botの名前が設定されます。
Appのインストール
いよいよAppのインストールです。
左側のメニューから[Install App]を選択し、[Install App to Workspace]をクリックします。
作成したAppが指定したワークスペースにアクセスするのを許可すると、グローバルIPを取得するコマンドの完成です。
実行結果
実行方法
今回作成したコマンド
/get_ip
をメッセージとして送信します。指定チャンネルから実行した場合
指定チャンネルから実行した場合のレスポンスは次の通りです。
IPアドレスが返ってきました!!!
ただ、自分のグローバルIPを調べてみると・・・違う。。
これはSlackのグローバルIPアドレスですね。。。残念ながら、自分のグローバルIPアドレスを取得することはできませんでした。
指定外のチャンネルから実行した場合
ちなみに、指定外のチャンネルから実行した場合のレスポンスは次の通りです。
これで、実行チャンネルを識別し、指定チャンネル外からは実行できないことが確認できました!
おわりに
結果、SlackからグローバルIPを取得することはできませんでしたが、SlackのIPは取得することができました。ただ、実行する度に違うIPアドレスが返ってくることも確認できました。今回とは関係ないですが、Slackは複数のグローバルIPアドレスを持っていることがわかりました。
前回に引き続き、SlackからLambdaを実行してみましたが、AWS Chatbotと比較してSlash commandsの良いところは個人DMで使えるというところかなと感じました。反対に、Slash commandsの場合は処理時間が3000msを越えるものは、まず応答を返すようにしないといけないところは少し面倒かな、、と思いました。
今回は、残念な結果に終わってしまったので、また違う方法を考えます。SlackからグローバルIPを取得するいい方法があるよっていう方いらっしゃいましたら、教えていただけると嬉しいです!
参考
- 投稿日:2020-09-18T03:40:07+09:00
GOPAHTとかGOROOTってなんやねん
GOROOTとGOPATHとは?ってなったのでメモ。
GOROOT
goをinstallした際に、go本体のソースが置かれる場所。
fmtとかosみたいな、goをinstallしたら初めから付いてくる標準パッケージのソースもここにある。以下のコマンドを叩くとマシンのGOROOTを吐いてくれる。
$ go env GOROOT /usr/local/Cellar/go/1.15/libexecGOPATH
後々追加した、外部のパッケージが置かれる場所。go getとかで入手したパッケージはここに置かれる。
これも以下のコマンドを叩くとマシンのGOPATHを吐いてくれる。
$ go env GOPARH /Users/userName/goまとめ
GOROOT:go本体+標準パッケージ
GOPATH:その他の外部パッケージってイメージかな?