go言語での文字列変換
go言語での文字列の他データ型への変換についてさらっと書きます。
こちらの公式ドキュメントに言及、一部和訳しています。
go言語の文字列(string型)は、rune型のスライス(rune)とbyte型のスライス(byte)にそれぞれ変換可能です。
rune型の実態はint32で、Unicodeのコードポイントを10進数表記で表します。
// rune is an alias for int32 and is equivalent to int32 in all ways. It is // used, by convention, to distinguish character values from integer values. type rune = int32
下記のように for range
で文字列を繰り返し処理にかけると、rune型で一文字ずつ取得できます。
str := "あいうえお" for _, s := range str { fmt.Printf("Type: %s, Value: %d\n", reflect.TypeOf(s), s) } /* OUTPUT: Type: int32, Value: 12354 Type: int32, Value: 12356 Type: int32, Value: 12358 Type: int32, Value: 12360 Type: int32, Value: 12362 */
各文字の出力がUnicodeのコードポイントの数値に合致していることがわかります。
またbyte型の実態はuint8で、1バイトを10進数表記で表します。
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is // used, by convention, to distinguish byte values from 8-bit unsigned // integer values. type byte = uint8
下記のように for 初期値; 条件; 変化式
で文字列を繰り返し処理にかけると、byte型で1バイトごとに取得できます。
str := "あいうえお" for i := 0; i < len(str); i++ { fmt.Printf("Type: %s, Value: %d\n", reflect.TypeOf(str[i]), str[i]) } /* OUTPUT: Type: uint8, Value: 227 Type: uint8, Value: 129 Type: uint8, Value: 130 Type: uint8, Value: 227 Type: uint8, Value: 129 Type: uint8, Value: 132 Type: uint8, Value: 227 Type: uint8, Value: 129 Type: uint8, Value: 134 Type: uint8, Value: 227 Type: uint8, Value: 129 Type: uint8, Value: 136 Type: uint8, Value: 227 Type: uint8, Value: 129 Type: uint8, Value: 138 */
このケースではバイトごとの繰り返しになるため、日本語のようなマルチバイト文字だと1文字ずつ抜き出せないことに注意してください。
rune型の場合はUnicodeコードポイントで1文字ごとに対応しているため、string(value)で文字列に変換できます。
数値をstring変換した場合は、その数値に対応するUnicodeコードポイントの文字に変換されます。
コードポイント範囲外の数値の場合は \ufffd
に変換されます。
num1 := 12450 fmt.Println(string(num1)) /* OUTPUT: ア */
stringを介さずにruneとbyteを変換する場合はunicode/utf8
パッケージのEncodeRune
、DecodeRune
などが使えるかと思います。
余談ですが、公式ドキュメントのコード例に元横綱の白鵬翔を発見しました。 ちょっと嬉しかったです。
パッケージ管理ツールrpm/yumことはじめ
今まで仕事でyum installでlinuxに色々なパッケージをインストールして使ってきたものの、正直仕組みの理解についてはかなりあやふやなままだったのである程度勉強してみました。
あくまでさわりの部分ですが、本記事はその備忘録ですd( ̄  ̄)
rpm/yumとは
rpm, yumはRed Hat系のLinuxディストリビューションで使用されるパッケージ管理、及びそのコマンドラインツールのことです。
rpmは依存関係管理機能を持たないのに対し、
yumは依存関係管理機能を持つという違いがあります。
yumはrpm形式のパッケージファイルを複数まとめて管理でき、なおかつ依存関係もイイ感じにしてくれるのでほとんどのケースではyumコマンドを用いてパッケージ管理することになると思います。
rpm
パッケージ管理ツール、コマンドラインツール、及びパッケージファイルの形式のこと。
Red Hat系のLinuxではrpm形式のパッケージファイルをインストールするなどして使用します。
rpmコマンドを用いて個々のパッケージファイルの操作などが行えます。ただしパッケージ間の依存関係は管理してくれません。
rpmbuildコマンド
rpmパッケージのビルドをするためのコマンドです。
SPECファイルと呼ばれるファイルを参照し、rpmパッケージを作成してくれます。 SPECファイルはパッケージを作成するための手順を記載したレシピのようなものです。
SRPMファイル
xxx.src.rpmの形式。これをもとにRPMのリビルドができます。
RPM作成に必要なSPECファイルなどのアーカイブとなっています。
SRPMファイルを用いたビルドはこのようにできます。
rpmbuild --rebuild xxx.src.rpm
その他rpmコマンド
rpm -ivh xxxxxxxxx.rpm
パッケージをインストールします。
rpm -qa
インストール済みのパッケージ一覧を表示します。
rpm -qi xxxxxxxxx.rpm
パッケージファイルの情報を表示します。
rpm -ql xxxxxxxxx.rpm
パッケージに内包されるファイル一覧を表示します。
yum
数多あるパッケージファイルをまとめて管理、依存関係もよしなにイイ感じにしてくれる便利なツールです。
リポジトリ(後述)ベースでパッケージを管理しており、リポジトリ自体も多く存在します。また自分で作成することも可能です。
操作もリポジトリベースで行われ、リポジトリからrpmファイルを検索したりインストールしたりといった操作になります。
リポジトリ
数あるパッケージを保管、配布している場所のことです。
/etc/yum.conf
や /etc/yum.repos.d/xxx.repo
ファイルでリポジトリを管理します。
yum repolist
コマンドによってリポジトリの表示ができます。ステータス無効になっているリポジトリのパッケージは基本的には操作できません。
repoファイルのリポジトリの中でenabled=0
となっているリポジトリは無効となります。
[samplerepo] name=sample-repo baseurl=http://sample/repo enabled=0
上記の例のように無効となっているリポジトリも、次のようにコマンドでリポジトリを指定することでパッケージのインストールなどが可能となります。
yum --enablerepo=samplerepo install xxxxxxx
createrepoコマンド
リポジトリを作成できるコマンドです。
rpmファイルを集めたディレクトリパスを指定し、対象ディレクトリのファイルでリポジトリを作成します。
# pwd /tmp/addrepo # ls -l ~~~~~... xxxxxxxxxxxxxxx.rpm ~~~~~... yyyyyyyyyyyyyy.rpm ~~~~~... zzzzzzzzzzzzzz.rpm
このコマンドにより対象ディレクトリにrepodataという、リポジトリのメタデータ情報を格納するディレクトリが作成されます。
# ls -l ~~~~~... xxxxxxxxxxxxxxx.rpm ~~~~~... yyyyyyyyyyyyyy.rpm ~~~~~... zzzzzzzzzzzzzz.rpm ~~~~~... repodata
createrepoコマンド実行後は、対象ディレクトリを上述のrepoファイルに記述し、yumコマンドにリポジトリとして認識させて使用する、という流れになります。
# vim /etc/yum.repos.d/add_repo.repo [add_repo] name=Add Repo baseurl=file:///tmp/addrepo
その他yumコマンド
yum repolist [all/enabled/disabled]
リポジトリの一覧を表示します。
- オプション未指定で有効のみ表示
- allで有効/無効全て表示
- enabledで有効のみ表示
- disabledで無効のみ表示
となります。
yum list [installed/available/extras]
パッケージ一覧を表示します。
- オプション未指定で全てのパッケージ表示
- installedでインストール済みのみ表示
- availableで未インストールの利用可能パッケージのみ表示
- extarsで利用不可のパッケージのみ表示
となります。
Go言語のコンパイル/アセンブリ周りについて
Go言語はコンパイル言語なのでソースコードをコンパイルしてバイナリを実行するという形になるのですが、 コンパイル → 実行ファイルまでの流れをよく理解できていなかったのでサラッと学んでみました。
こちらの記事を要所要所で翻訳しつつ、噛み砕いたものを記事にしています。
間違い等あればご指摘いただければ幸いですm( )m
コンパイル
ソースをコンパイルしてgoアセンブリを作成し、それをアセンブラが解釈してオブジェクトファイルを作成します。
最終的にはオブジェクトファイルやアーカイブライブラリをリンカーがまとめてリンクし、実行可能バイナリファイルを作成するという手順です。
アセンブリを覗いてみる
以下のようなgoファイルを用意します
package calculate func add(m int, n int) int { return m + n } func sum(m int, n int) int { return m - n }
以下サンプルでコンパイルしてオブジェクトファイルを作ります。
ソースコード → アセンブリ → オブジェクトファイルという作成の流れを踏むのですが、
-S
オプションによって標準出力にアセンブリが出力されます。
go tool compile -S add.go ↓ "".add STEXT size=16 args=0x10 locals=0x0 funcid=0x0 align=0x0 leaf 0x0000 00000 (add.go:3) TEXT "".add(SB), LEAF|NOFRAME|ABIInternal, $0-16 0x0000 00000 (add.go:3) FUNCDATA ZR, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (add.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (add.go:3) FUNCDATA $5, "".add.arginfo1(SB) 0x0000 00000 (add.go:3) FUNCDATA $6, "".add.argliveinfo(SB) 0x0000 00000 (add.go:3) PCDATA $3, $1 0x0000 00000 (add.go:4) ADD R1, R0, R0 0x0004 00004 (add.go:4) RET (R30) 0x0000 00 00 01 8b c0 03 5f d6 00 00 00 00 00 00 00 00 ......_......... "".sum STEXT size=16 args=0x10 locals=0x0 funcid=0x0 align=0x0 leaf 0x0000 00000 (add.go:7) TEXT "".sum(SB), LEAF|NOFRAME|ABIInternal, $0-16 0x0000 00000 (add.go:7) FUNCDATA ZR, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (add.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (add.go:7) FUNCDATA $5, "".sum.arginfo1(SB) 0x0000 00000 (add.go:7) FUNCDATA $6, "".sum.argliveinfo(SB) 0x0000 00000 (add.go:7) PCDATA $3, $1 0x0000 00000 (add.go:8) SUB R1, R0, R0 0x0004 00004 (add.go:8) RET (R30) 0x0000 00 00 01 cb c0 03 5f d6 00 00 00 00 00 00 00 00 ......_......... go.cuinfo.packagename. SDWARFCUINFO dupok size=0 0x0000 63 61 6c 63 75 6c 61 74 65 calculate gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 ........ "".add.arginfo1 SRODATA static dupok size=5 0x0000 00 08 08 08 ff ..... "".add.argliveinfo SRODATA static dupok size=2 0x0000 00 00 .. "".sum.arginfo1 SRODATA static dupok size=5 0x0000 00 08 08 08 ff ..... "".sum.argliveinfo SRODATA static dupok size=2 0x0000 00 00
以下のようにgo build
コマンドに-gcflags -S
オプションをつけても、同様にアセンブリが出力されます。
go build -gcflags -S add.go
goアセンブリには色々なディレクティブが存在するわけですが、以下の2つはガベージコレクタが使用するもので、コンパイラによってアセンブリに挿入されます。
- FUNCDATA
- PBDATA
またアーキテクチャによってアセンブリのシンボルは異なるのですが、以下の仮想レジスタを指すシンボルは全アーキテクチャで統一されています。
- FP: Frame pointer: arguments and locals.
- PC: Program counter: jumps and branches.
- SB: Static base pointer: global symbols.
- SP: Stack pointer: the highest address within the local stack frame.
goアセンブリは基本的に疑似命令で構成されています。
命令にはアセンブル後の機械語と1対1で対応しているものもあれば、直接対応していないものもあります。
関連コマンドetc
go tool compile
goパッケージをコンパイルしてオブジェクトファイルを生成します。
-S
オプションで、生成過程のアセンブリを標準出力に出すことができます。
compile command - cmd/compile - Go Packages
go tool link
オブジェクトファイルやアーカイブライブラリをリンカーに渡し、実行ファイルを生成(リンク)します。
link command - cmd/link - Go Packages
go tool nm
オブジェクトファイル、アーカイブライブラリ、実行ファイルで定義、使用されているシンボルのリストを返します。
アドレス / 型 / シンボル名
という書式になっています。
未定義シンボル(U)のアドレスは省略されます。
nm command - cmd/nm - Go Packages
go tool objdump
実行ファイルを逆アセンブルします。
出力されるのはリンク後の実行ファイルのアセンブリのため、オブジェクトファイルをgo tool compile -S
した時とは別の結果となります。
Go言語でのジェネリクス(1.18から)
Go言語ではバージョン1.18から待望のジェネリクスが実装されたのですが、
普段の業務でジェネリクスを使うことがあまりなく仕様をよく分かっていなかったので
備忘録的に残しておこうと思います。
ジェネリクスとは
関数の引数となるデータ型が複数考えられる時、考えられる全てのデータ型を受け入れるように関数を実装できる仕組みのこと。
関数呼び出し時に引数と一緒に引数の型も指定する。
関数定義時点では引数の型が分からないといった場合に、柔軟性のある関数を実装することができます。
Goでの実装例
例えば下のSumInt関数とSumStringは、それぞれ引数の2つのint, stringを足し合わせた結果を返す関数です。
当然、SumIntの引数にstring型は指定できないし、SumStringの引数にint型は指定できません。
func SumInt(num1 int, num2 int) int { return num1 + num2 } func SumString(str1 string, str2 string) string { return str1 + str2 }
ここで、引数の型は関数実行時にしかわからないけど、とにかく引数の2つの値を足し合わせた結果を返す関数を定義したい、とします。
ここでジェネリクスを使った関数を定義します。
func SumIntOrString[T int | string](arg1, arg2 T) T { return arg1 + arg2 }
ここで定義したT
は型パラメータと呼ばれるもので、実行時に取りうるTの型を表します。
この関数では2つの引数と返り値の型がTとなります。
int | string
はユニオンと呼ばれるもので、intかstringのどちらかになれるよ、という意味です。そのものずばり、型制約と呼ばれたりします。
この関数を実行してみましょう。
func main() { res := SumIntOrString[int](4, 5) fmt.Println(res) // 9 res := SumIntOrString[string]("qwe", "rty") fmt.Println(res) // qwerty }
こんな感じで、関数名の直後に型引数[]
で型パラメータの型を決めてあげます。
また、引数が型パラメータで定義されている場合は関数実行時の型引数は省略できます。コンパイラが型パラメータを参考に取りうる型を推測してくれるからです。
func main() { res := SumIntOrString(4, 5) // 型引数なしでもOK fmt.Println(res) // 9 res := SumIntOrString("qwe", "rty") // 型引数なしでもOK fmt.Println(res) // qwerty }
引数が型パラメータで定義されていない場合(引数自体がない場合など)は、型引数は省略できないのでご注意を。
ジェネリクス関数を複数定義するときに毎回同じ型制約を指定する時などは、いちいち同じ型制約を全ての関数に書いていると煩雑です。
そんな時はインターフェースで型制約を定義して使いまわしてしまいましょう。
type DefaulOption interface { int | string } func SumIntOrString[T DefaulOption](arg1, arg2 T) T { return arg1 + arg2 }
comparableやanyなど、組み込み済みの型制約インターフェースも使用できます。
// any is an alias for interface{} and is equivalent to interface{} in all ways. type any = interface{} // comparable is an interface that is implemented by all comparable types // (booleans, numbers, strings, pointers, channels, arrays of comparable types, // structs whose fields are all comparable types). // The comparable interface may only be used as a type parameter constraint, // not as the type of a variable. type comparable interface{ comparable }
Underlying Type
型パラメータの型にチルダが付いているパターン。このチルダはUnderlying Typeと呼ばれます。
func SumIntOrString[T ~int | ~string](arg1, arg2 T) T { return arg1 + arg2 }
これはUnderlying Typeがint/stringの型ならOKという意味です。
例えばこんな感じです。
func SumIntOrString[T int | string](arg1, arg2 T) T { return arg1 + arg2 } func SumUnderlyingIntOrString[T ~int | ~string](arg1, arg2 T) T { return arg1 + arg2 } type NewInt int // NewIntはUnderlying Typeがintの型 func main() { var num1 NewInt = 3 var num2 NewInt = 5 fmt.Println(SumIntOrString(num1, num2)) // これはエラー fmt.Println(SumUnderlyingIntOrString(num1, num2)) // これはセーフ }
Underlying Typeの分かりやすい説明記事を発見したのでご参考までに。