vlang入門
The V Programming Language
https://github.com/vlang/v
Hello World
https://docs.vlang.io/hello-world.html
任意のディレクトリで v init
code:main.v
module main
fn main() {
println('Hello World!')
}
GitHub Copilotによると
module main - このコードがmainモジュールに属していることを宣言しています。V言語では、すべてのプログラムは実行のエントリーポイントとなるmainモジュールを持つ必要があります。V言語のモジュールは、他の言語のパッケージや名前空間と同様に、コードを整理する方法を提供します。
fn main() - メイン関数を定義しています。これはV言語プログラムの実行開始点です。プログラムを実行すると、V言語のランタイムは自動的にこの関数を最初に呼び出します。fnキーワードはV言語で関数を宣言するために使用され、main()はこの関数がパラメータを取らないことを示しています。
println('Hello World!') - メイン関数内で、この行は組み込み関数のprintlnを呼び出し、「Hello World!」というテキストをコンソールに出力し、改行文字を追加します。テキストはシングルクォートで囲まれており、これはV言語で文字列リテラルを定義する標準的な方法です。
直接実行
v run src/main.v
コンパイル
v src/main.v
src/main 実行ファイルが作られる。
fn main() declaration can be skipped in single file programs. This is useful when writing small programs, "scripts", or just learning the language. For brevity, fn main() will be skipped in this tutorial.
単一ファイルであればmain()はいらないらしい。同一モジュールに複数ファイルある場合のエントリポイントの決定に使われるという感じだろうか
println
https://docs.vlang.io/builtin-functions.html#println
println is a simple yet powerful builtin function, that can print anything: strings, numbers, arrays, maps, structs.
code:main.v
struct User {
name string
age int
}
println(1) // "1"
println('hi') // "hi"
println(1, 2, 3) // "1, 2, 3"
println(User{ name: 'Bob', age: 20 }) // "User{name:'Bob', age:20}"
構造体に独自の文字列フォーマッティングを定義することもできる。Javaの toString()みたいなものか
code:main.v
struct Color {
r int
g int
b int
}
pub fn (c Color) str() string {
return '{${c.r}, ${c.g}, ${c.b}}'
}
red := Color{
r: 255
g: 0
b: 0
}
println(red)
dump()
https://docs.vlang.io/builtin-functions.html#dumping-expressions-at-runtime
おもしろい機能。引数の値とソースコード位置を標準出力に出しつつ、評価結果をそのまま戻している
コンソールデバッグのために行番号が増減しない
これ他の言語でも便利そう
code:main.v
fn factorial(n u32) u32 {
if dump(n <= 1) {
return dump(1)
}
return dump(n * factorial(n - 1))
}
fn main() {
println(factorial(5))
}
code:stdout
src/main.v:2 n <= 1: false
src/main.v:2 n <= 1: false
src/main.v:2 n <= 1: false
src/main.v:2 n <= 1: false
src/main.v:2 n <= 1: true
src/main.v:3 1: 1
src/main.v:5 n * factorial(n - 1): 2
src/main.v:5 n * factorial(n - 1): 6
src/main.v:5 n * factorial(n - 1): 24
src/main.v:5 n * factorial(n - 1): 120
120
dump は関数っぽく見えるが関数ではない
dump はコンパイル時キーワード AST上で DumpExpr として引っ掛けられる
https://github.com/vlang/v/blob/af947f1af5e87f9d3214b25a73001b2355241914/vlib/v/token/token.v#L323
DumpExpr はトレース用のコードに置き換えられる。多分Cのコードに変換されている?
https://github.com/vlang/v/blob/af947f1af5e87f9d3214b25a73001b2355241914/vlib/v/gen/c/dumpexpr.v#L7
カッコ内の式を文字列化する必要があるので、ASTを触れるコンパイル時にやるしかなさそう。
power_assert的な仕事
一方 printlnはビルトイン関数 https://github.com/vlang/v/blob/master/vlib/builtin/builtin.c.v#L324
複数ファイルから成るプロジェクト
https://docs.vlang.io/running-a-project-folder-with-several-files.html
v run ./src のようにディレクトリを指定すればエントリポイントを探して実行される
関数
複数戻り値を返せる
code:main.v
fn foo() (int, int) {
return 2, 3
}
a, b := foo()
println(a) // 2
println(b) // 3
c, _ := foo() // ignore values using _
変数
code:main.v
name := 'Bob'
age := 20
large_number := i64(9999999999)
println(name)
println(age)
println(large_number)
変数は常に初期値を伴って宣言される。
変数を宣言できるのは関数スコープのみ
Unlike most other languages, V only allows defining variables in functions. By default V does not allow global variables.
変数はデフォルトでイミュータブル。mut宣言で変更可能になる。
code:main.v
mut age := 20
println(age)
age = 21
println(age)
Unlike most languages, variable shadowing is not allowed. Declaring a variable with a name that is already used in a parent scope will cause a compilation error.
変数のシャドーイングが許されない。
code:main.v
fn main() {
a := 10
{
a := 20 // error: redefinition of a
}
}
Option/Result types
https://docs.vlang.io/type-declarations.html#optionresult-types-and-error-handling
Option/Result周りがめちゃくちゃ手厚い。
型に ? プレフィックスがついたらOption型、!プレフィックスがついたらResult型になる。すごい
関数がOptionかResultを返した場合は or ブロックで受ける必要がある
code:main.v
fn (r Repo) find_user_by_id(id int) !User {
for user in r.users {
if user.id == id {
// V automatically wraps this into a result or option type
return user
}
}
return error('User ${id} not found')
}
// A version of the function using an option
fn (r Repo) find_user_by_id2(id int) ?User {
for user in r.users {
if user.id == id {
return user
}
}
return none
}
fn main() {
repo := Repo{
users: User{1, 'Andrew'}, User{2, 'Bob'}, User{10, 'Charles'}
}
user := repo.find_user_by_id(10) or { // Option/Result types must be handled by or blocks
println(err)
return
}
println(user.id) // "10"
println(user.name) // "Charles"
user2 := repo.find_user_by_id2(10) or { return }
// To create an Option var directly:
my_optional_int := ?int(none)
my_optional_string := ?string(none)
my_optional_user := ?User(none)
}
関数呼び出しの末尾に ! をつけるとエラーハンドリングを呼び出し元に移譲する(その関数自身がResultを返す)
同様に関数呼び出しの末尾に ? をつけると
code:main.v
import net.http
fn f(url string) !string {
resp := http.get(url)!
return resp.body
}
これは次のコードと等価
code:main.v
import net.http
fn f(url string) !string {
return http.get(url) or { return err }
}
Option/Resultを返したくない場合は任意の値を返す。戻り値なしなら早期リターン。ループ中ならbreakやcontinueもできる
code:main.v
fn foo() (string, string) {
v1 := foo_result() or { return 'default' }
}
orブロックがreturnするだけなら省略して値をそのまま書けばその値にフォールバックする
code:main.v
fn foo() (string, string) {
v1 := foo_result() or { 'default' }
}
if の評価結果がResult/Optionの場合は、自動的にelse が or に対応する
code:main.v
import net.http
if resp := http.get('https://google.com') {
println(resp.body) // resp is a http.Response, not an option
} else {
println(err)
}
Expressions/Statements
ifは式になる
code:main.v
num := 777
s := if num % 2 == 0 { 'even' } else { 'odd' }
println(s) // "odd"
match も式になる
code:main.v
number := 2
s := match number {
1 { 'one' }
2 { 'two' }
else { 'many' }
}
Assertion
https://docs.vlang.io/testing.html#asserts-that-do-not-abort-your-program
[assert_continues]タグを使うと本番ビルド時にはエラーログだけ出すように変換される。表明しやすくて便利
code:main.v
@assert_continues
fn abc(ii int) {
assert ii == 2
}
for i in 0 .. 4 {
abc(i)
}
Testing
https://docs.vlang.io/testing.html#test-files
- All test functions have to be inside a test file whose name ends in _test.v.
- Test function names must begin with test_ to mark them for execution.
- Normal functions can also be defined in test files, and should be called manually. Other symbols can also be defined in test files e.g. types.
- There are two kinds of tests: external and internal.
Internal tests must declare their module, just like all other .v files from the same module. Internal tests can even call private functions in the same module.
- External tests must import the modules which they test. They do not have access to the private functions/types of the modules. They can test only the external/public API that a module provides.
テストの実行は v test コマンド。 v test . でカレントディレクトリ以下すべてを走査する。
Memory management
https://docs.vlang.io/memory-management.html
4種類のメモリ管理
1. デフォルトのトレースGC
2. autofree
コンパイル時にfree()が挿入される
90%くらいはautofreeでメモリ解放され、残りは通常のGCでカバーするらしい。高コストなGCの使用を最小限にする
まだ開発中
3. 手動制御 (-gc none)
4. 事前バッファ -prealloc
小さなオブジェクト用のメモリを事前に確保しておく
基本はデフォルトでよさそう
作ってみた
lacolaco/vlang-mcp-server: Just for learning https://github.com/lacolaco/vlang-mcp-server
lacolaco/vdice https://github.com/lacolaco/vdice
うれしさ
Goっぽい素朴さ
構文について覚えることは少ない
Option/Resultの第一級サポート
テストランナーは素朴で簡単
assert が使いやすい
dumpよい
つらさ
ポリモーフィズムよわい
ジェネリクス周りがまだ貧弱
かゆいところがかゆい
sumtype状態のまま json.encode すると _type フィールドが勝手に生えるの困りすぎる
多段にネストしうるJSONの扱いがめんどい
TypeScriptの型の柔軟さ、ありがたさを再確認できた
vlang/setup-v 死んでる
vpmのパッケージが少ない・古い
まだWIPな機能が多い