Ruby
Go
golang

これからGolang開発を行うRubyistたちへ

背景

  • APIサーバー開発などを今までRuby(主にRails)で開発していたRubyistがサーバーサイド全部をフルGoLangで実装している会社に転職して、2週間が経ち今までのGolang開発で学んだことをまとめました。
  • これからGoLang開発をするRubyistたちが、「自分と同じようにつまずくかもしれない」または「Golangを本格的に書き始める前に知っておきたかったなあ」と思うところがまとめてあります。
  • 最近、Ruby on RailsやRuby開発を進めている会社でも、必要な箇所でGoLang開発を行なっていると聞いたので、Golang開発に興味があるRubyistが大勢いるじゃないかと思い、転職をきっかけに記事を書かせていただきました。
  • 「Golang開発やってみたい!」「会社で導入することになった」というRubyistたちの最初のステップになれば幸いです。
  • Rubyはこうだけど、Golangだとこういう風に書くよと比較のコードも混ぜています。

対象

  • Rubyを書いたことがありGolangを書いたことがないけど、これからGoLang開発始めよう、または興味がある人。

インストール法は?

  • goenv をおすすめします。
    • rbenv とほぼ同じと思ってもらっていいです。
  • バージョンの切り替えも簡単にできます。
    • localglobal のオプションもあるので、プロジェクトごとやデフォルトでもバージョン設定も可能です。
  • rehash と入力すると、 .ruby-version と同じく.go-version というファイルもできます。プロジェクトでなんのバージョンが使用されているのかチーム内で共有することができます。
$ git clone https://github.com/dataich/goenv.git ~/.goenv

# ログインシェルの設定ファイルに
export PATH="$HOME/.goenv/bin:$PATH"
eval "$(goenv init -)"

$ goenv install 1.7 # お好みのバージョン
$ goenv global 1.7

基本的な文法

型に関しては、ここでは省略します。

  1. Goでは、変数と関数は最初が大文字で始まるキャメルケースが使われています。
    • Rubyは、スネークケースですよね。
  2. Goでは、publicにしたいプロパティ/関数を大文字を使います。privateにしたいプロパティ/関数は小文字にし、違うパッケージからのアクセスすることはできません。
  3. Goでは、Rubyのような破壊的メソッドを書くときは、構造体(Rubyでいうとクラス)のポインターをレシーバーにしないといけません。
    • Rubyにはポインタの値渡しがないので、Goを勉強すると最初にここでつまづくと思います。
  4. iota は、型なしの整数の連番を生成します。
    • これは好き嫌いが別れると思いますが、他の言語にはない文法なので、紹介します。
  5. Goでは、定数は const で宣言します。
  6. Goでは、構造体のインスタンスを作る方法がいくつかあります。
    • new を使うと 空のポインタ型の構造体のインスタンスを作成します。
    • 構造体名{プロパティ: 値}空の構造体を作ります。
User.go
package main

import "fmt"

// 定数
const (
    NonPremium = iota
    Premium
)

type User struct {
    firstName     string
    PremiumStatus int
}

// インスタンスメソッド (レシーバー名 関数名(引数) 返り値
//  構造体の中身を変更させるものなので、レシーバーをポインタ型にしないといけない。
func (u *User) UpdateToPremium() {
    u.PremiumStatus = Premium
}

func main() {
    user := &User{FirstName: "hoge", PremiumStatus: 0}
    // これだと、ポインタ型の空の構造体を作る。FirstNameとPremiumStatusには、空文字と0が入る。
    // user := new(User) 
    user.UpdateToPremium()
    fmt.Println(user) // &{hoge 1}
    fmt.Println(user.firstName) // hoge
}

User.rb
class User 
  # 定数
  NON_PREMIUM = 0
  PREMIUM = 1

  def initialize(first_name, premium_status)
    @first_name = first_name
    @premium_status = premium_status
  end

  private 
  # 破壊的メソッド
  def update_to_premium!
    @premium_status = PREMIUM
  end
end

user = User.new('hoge', NON_PREMIUM)
user.update_to_premium!

その他の文法

1. 可変長変数

  • 便利なので、Golangでの書き方を共有
splat_operator.go
package main

import "fmt"

func main(){
  SplatOperator("a", "b", "c")
}

func SplatOperator(args ...string){
  fmt.Println(args) // => [a b c]
}
splat_operator.rb
def splat_operator(*args)
 p args
end

splat_operator('a', 'b', 'c') # => [a b c]

自分がつまずいたエラー集

1. Golangでは、ダブルクオテーションとバッククオートで表現する。

  • Golangではダブルクオテーションとバッククオートを使いますが意味が違います。
  • 自分がRubyを書くとき、文字列の中で変数を展開するとき以外は、シングルクオテーションを使いますが、その癖でビルドしてみるとエラーの嵐が...。
  • シングルクオテーションのほうが速度が上とシングルクオテーションを書くRubyistは注意です。
  • バッククオートはエスケープシーケンスを解釈しないかつ、改行はそのままテキスト中でも改行になるため、ヒアドキュメント(複数行対応可)です。
  • 式展開はどちらもないです。
sample.go
package main

import "fmt"

func main(){
  fmt.Println('hoge') // これはエラー
}

2. ポインタ型のスライスからは、要素を取得することができない。

  • 2週間でここに一番詰まりました。
  • GoLangには、ポインタの概念があり、最初Rubyistがつまずくのかなと思っています。
  • GoLangには、「スライス型」という可変長配列があります。
    • 配列もありますが、それは要素数などが決められていて、実際に業務で書く時には、要素が固定で決められることはあまりないと思うので、このスライス型がよく使われます。
  • 実際につまづいたエラーは下のものになります。
# command-line-arguments
./sample.go:7:18: invalid operation: fuga[0] (type *[]int64 does not support indexing)
Sample.go
package main

import "fmt"

func main(){
  hoge := []int64{2, 3, 4} // => スライス型を定義
  fuga := &hoge // => わざとポインタ型にしています。
  fmt.Println(fuga[0]) // => invalid operation: fuga[0] (type *[]int64 does not support indexing)
}
  • エラー文にあるように、ポインタ型のスライスはインデックスをサポートしていない = 使えないよということです。
  • なので、ポインタ型から値型に戻さないといけないです。
Sample.go
package main

import "fmt"

func main(){
  hoge := []int64{2, 3, 4}
  fuga := &hoge // => わざとポインタ型にしています。
  fmt.Println(*fuga[0]) // => 2 ポインタから値に戻しています。
}

3. インスタンスメソッド内のレシーバーの名前は、thisselfじゃだめ。

receiver name should be a reflection of its identity; don't user generic name as such or this
  • これはエラーではなく、golintで注意されたwarningです。
  • 下のようなUser構造体を作り、インスタンスメソッドを生やして、自身のオブジェクトにアクセスするときに、Rubyだと self と書いてしまうと思います。
user.rb
class User < ApplicationRecord
  def name_with_prefix
    "Mr. #{self.name}" 
  end
end
user.go
package model

type User struct{
  Name   string
  Gender int64
}

func (s *Student) NameWithPrefix() string {
  return "Mr. " + s.Name
}

// これはwarning
// func (self *Student) NameWithPrefix() string {
//   return "Mr." + self.Name  ここはwarning
// }

4. 構造体のインスタンスを作成する方法はいくらでもある。

  • Rubyだとクラスのインスタンスを作る方法は、 下のように new しかないですよね。
sample.rb
class Sample 
  attr_reader :title

  def initialize(title)
    @title = title
  end
end

sample = Sample.new('sample')
p sample.title
  • しかし、GoLangでは、下のような構造体があったときに、下の数だけ空の構造体を取得するやり方があります。Rubyistに気をつけてほしいのが、 newポインタ型の構造体 を返すことです。
  • Rubyではnewでインスタンス取得できると思いますが、Rubyにはポインタの概念がないので、Golangでnewとすると、ポインタ型ではない空の構造体が取得できると思われがちですが、ポインタ型なので注意が必要です。
Vehicle.go
type Vehicle struct{
  Name string
}
main.go
package main

func main(){
  // 同じ意味 空の構造体を返す
  var v Vehicle = Vehicle{} 
  vehicle := Vehicle{}

    // 同じ意味 Golangでの`new`は、 ポインタ型を返すので注意 
  vehicle := new(Vehicle)
  vehicle := &Vehicle{} 
}

5. Map初期化

  • ここでいうMapは、Rubyでいうハッシュです。
  • 注意してほしいのは、Mapは宣言に加えて、一度初期化を行わないといけないことです。
  • Rubyだと初期化する必要は全くない & Map型だけ初期化が必要になってくるので、ここでまとめておきました。
  • 参考: http://otiai10.hatenablog.com/entry/2014/08/09/154256
panic: runtime error: assignment to entry in nil map
Sample.go
package main

import "fmt"

func main() {
    var m map[string]string
    fmt.Println(m) // map[]

    /* panic: runtime error: assignment to entry in nil map
       m["hoge"] = "fuga"
       fmt.Println(m)
    */

    // 初期化すればOK
    m = map[string]string{}
    fmt.Println(m) // map[]

    m["hoge"] = "fuga"
    fmt.Println(m) // map[hoge:fuga]
}


まとめ

  • たった2週間ですが、まとめてみると多くのところに詰まったと思っています。
  • 時間ができたときは、さらに自分が詰まったところを残しておこうと思います。
  • これからGoLangデビューするRubyistの参考になれば幸いです。