Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
161
Help us understand the problem. What are the problem?

V プログラミング言語

はじめに

The V Programming Language公式ドキュメント の和訳だよ

情報はすべて↑から
コンパイルが速いこととC/C++との相互変換がウリの模様 (C/C++ 勢なので注目せざるを得ない
2019 年 4 月にアーリーアクセス、同年 6 月 22 日にオープンソースで公開済み
すでに V 自身で V コンパイラは作成されている
もともと GUI アプリ用につくられたので
クロスプラットフォームな GUI ライブラリとかもあったりする

私も和訳で閲覧数を稼ぐコスいことしてみる

ただドキュメントが完全ではないのでまだ把握しきれてない

ちょくちょく更新してるのでストックしとくといいかも

以下から訳文


V ドキュメント

導入

V は保守しやすいソフトウェアを構築するために設計された静的型付け言語です。

Go にとてもよく似通っており、Oberon、Rust、Swift にも影響されています。

V は非常にシンプルな言語です。
1 時間ほどかけてこのドキュメントを読めば、言語の全貌がちょっとつかめるでしょう。

シンプルですが、開発者にとって大きな力になります。
他の言語でできることなら、V もできますよ。

ソースからのインストール

最新で最高な V を入手する主な方法は、ソースからのインストール です。これは 簡単 で、通常は たった数秒 で完了します。

Linux、macOS、FreeBSD など:

gittccgccclang のような C コンパイラ、そして make が必要です。

git clone https://github.com/vlang/v
cd v
make

Windows:

git と、tccgccclangmsvc のような C コンパイラが必要です。

git clone https://github.com/vlang/v
cd v
make.bat -tcc

注: make.bat-gcc-msvc-clang のいずれかを渡すことで、お好みの異なる C コンパイラにできます。しかし -tcc が小さく、高速で、インストールが簡単です (V は自動でビルド済みバイナリをダウンロードします)。

このフォルダを環境変数の PATH に追加することを推奨します。これは v.exe symlink でも実行できます。

Android

Android 上での V のグラフィカルアプリの実行は、vab を介してもできます。

V Android の依存関係は、VJava JDK >= 8、Android SDK + NDK です。

  1. 依存関係 (vab を参照) をインストール
  2. Android デバイスに接続
  3. 以下を実行
git clone https://github.com/vlang/vab && cd vab && v vab.v
./vab --device auto run /path/to/v/examples/sokol/particles

さらなる詳細とトラブルシューティングは、vab GitHub レポジトリ を閲覧してください。

Hello World

fn main() {
    println('hello world')
}

このスニペットを hello.v ファイルに保存して、v run hello.v を実行してみましょう。

こちら で説明している通り、ここでは V を v symlink でシンボリックリンクしていることを前提にしています。
まだ設定していない場合、V へのパスは手動でタイプする必要があります。

おめでとうございます——初めての V プログラムを書いて、実行しました!

v hello.v で実行せずにプログラムをコンパイルできます。サポートしている全コマンドは v help でご覧ください。

他の言語 (C、Go、Rust など) と同じで、main はエントリーポイントです。

println はいくつかある組み込み関数の一つです。これは標準出力へ値を出力します。

fn main() の宣言は単一ファイルプログラム内では省略できます。これは小さいプログラムである「スクリプト」を書くときや、言語について学ぶだけのときに役立ちます。簡潔にするため、このチュートリアルでは fn main() は省略されます。

すなわち "hello world" プログラムは V だとここまでシンプルにできます。

println('hello world')

複数ファイルのプロジェクトフォルダを実行

あるフォルダにいくつかの .v ファイルがあり、そのうちの 1 つには main() 関数が、他のファイルにはヘルパー関数があるとします。それらは課題別に整理されているかもしれませんが、まだ再利用可能な個別のモジュールになるほどの構造ではなく、それらすべてを 1 つのプログラムにコンパイルしたいとします。

他の言語では、インクルードやビルドシステムを使ってすべてのファイルを列挙し、それらを別々にオブジェクトファイルにコンパイルしてから、単一実行ファイルにリンクする必要があります。

しかし V では、.v ファイルのフォルダ全体をまとめてコンパイルし実行でき、v run . だけで済みます。パラメータを渡すこともできるので、v run . --yourparam some_other_stuff のようにすることができます。

上記は、まずファイルを単一プログラム (フォルダ/プロジェクトにちなんだ名前) にコンパイルし、CLI 引数として --yourparam some_other_stuff を渡してプログラムを実行します。

プログラムからは、以下のように CLI 引数を利用できます。

import os

println(os.args)

注: 実行後、V は生成した実行ファイルを削除します。実行ファイルを残しておきたい場合は、代わりに v -keepc run . を使うか、v . で手動コンパイルしてください。

注: V コンパイラのフラグはすべて run コマンドより前で渡してください。ソースファイル/フォルダより後にあるものはそのままプログラムに渡され、V は処理しません。

コメント

// これは単行コメント。
/*
これは複行コメント。
   /* ネストできる。 */
*/

関数

fn main() {
    println(add(77, 33))
    println(sub(100, 50))
}

fn add(x int, y int) int {
    return x + y
}

fn sub(x, y int) int {
    return x - y
}

更に、型は引数名の後に来ます。

Go や C のように、関数はオーバーロードできません。これはコードが単純になり保守性と可読性が向上します。

関数はその宣言より前で使用できます。addsubmain の後に宣言しても、main から呼び出すことができます。これは V のすべての宣言に当てはまり、ヘッダーファイルの必要性やファイルの順序と宣言について考慮する必要がなくなります。

複数の値を返す

fn foo() (int, int) {
    return 2, 3
}

a, b := foo()
println(a) // 2
println(b) // 3
c, _ := foo() // `_` で値を無視します

シンボルの可視性

pub fn public_function() {
}

fn private_function() {
}

関数は、デフォルトで非公開 (エクスポートしないよう) になっています。他のモジュールから使用できるようにするには、前に pub を付けます。これは定数や型定義でも同じです。

注意: pub は名前付きモジュールでのみ使用できます。詳しくは モジュール をご参照ください。

変数

name := 'Bob' 
age := 20
large_number := i64(9999999999)
println(name)
println(age)
println(large_number) 

変数は := で宣言と初期化が行われます。これは V で変数を宣言する唯一の手段です。つまり、変数は必ず初期値があるということです。

変数の型は右側の値から推論されます。他の型に強制するには、型変換を使います。T(v) と書くと値 v を型 T に変換します。

他の多くの言語とは異なり、V は関数内でしか変数の定義を許可しません。グローバル (モジュールレベルな) 変数は使用できません。V に大域な状態はないのです (詳細は 既定で純粋関数 を参照)。

他のコードベースとの一貫性のため、すべての変数と関数の名前は snake_case スタイルを使ってください。型名には PascalCase を使ってください。

可変の変数

mut age := 20
println(age)
age = 21
println(age)

変数の値を変更するには = を使用します。V での変数はデフォルトでイミュータブルです。変数の値を変えられるようにするには、mut をつけて宣言する必要があります。

最初の行の mut を消してコンパイルしてみると分かるでしょう。

初期化と代入の違い

:== の (大事な) 違いに注意してください。 := は宣言と初期化に使いますが、= は代入に使います。

fn main() {
    age = 21
}

上のコードはコンパイルできません。変数 age は宣言されていないからです。V ではすべての変数は宣言されている必要があります。

fn main() {
    age := 21
}

複数の変数の値を一行で変更できます。以下のように、一時変数を介さずに値を入れ替えできます。

mut a := 0
mut b := 1
println('$a, $b') // 0, 1
a, b = b, a
println('$a, $b') // 1, 0

宣言時のエラー

開発モードでのこのコードは "未使用の変数" の警告を出します。実動モード (v -prod foo.v のように v に -prod を渡すと有効化) では、Go と同じくコンパイルできません。

fn main() {
    a := 10
    if true {
        a := 20 // エラー: 変数の再定義
    }
    // 警告: 未使用の変数 `a`
}

多くの言語と異なり、変数のシャドーイングは許可されていません。親スコープですでに使用されている名前の変数を宣言するとコンパイルエラーになります。

インポートしたモジュールのシャドーイングは可能であり, こちらは以下のように大変有用なことがあります。

import ui
import gg

fn draw(ctx &gg.Context) {
    gg := ctx.parent.get_ui().gg
    gg.draw_rect(10, 10, 100, 50)
}

V の型

プリミティブ型

bool

string

i8   i16  int  i64      i128 (そのうち)
byte u16  u32  u64      u128 (そのうち)

rune // Unicode コードポイントに相当

f32 f64

voidptr, size_t // C との相互運用のためのもの

any // C の void* や Go の interface{} のようなもの

(訳注: Unicode コードポイントではなく Unicode スカラー値か?)

注意として、C や Go と異なり、int は常に 32 ビット整数になります。

V では全演算子の両側が同じ型の値でなければならないという規則がありますが、これには例外があります。
一方の小さいプリミティブ型が他方の型のデータ範囲に完全に収まる場合、自動的に拡大変換できます。
以下にこの変換が可能な経路を示します。

   i8 → i16 → int → i64
                  ↘     ↘
                    f32 → f64
                  ↗     ↗
 byte → u16 → u32 → u64 ⬎
      ↘     ↘     ↘      ptr
   i8 → i16 → int → i64 ⬏

例えば、int の値は f64i64 へ自動的に拡大変換できますが、u32 にはできません (u32 では負値の符号が失われてしまいます)。しかし、int から f32 への拡大変換は現在自動的に行われます (しかし、大きな値では精度が低下する可能性があります)。

1234.56 のようなリテラルは特別な方法で扱われます。これらは型の拡大変換にはつながりませんが、型の決定が必要な場合はデフォルトでそれぞれ intf64 に設定されます。

u := u16(12)
v := 13 + u    // v の型は `u16` - 拡大変換なし
x := f32(45.6)
y := x + 3.14  // x の型は `f32` - 拡大変換なし
a := 75        // a の型は `int` - int リテラルなのでデフォルト通り
b := 14.7      // b の型は `f64` - float リテラルなのでデフォルト通り
c := u + a     // c の型は `int` - `u` の値を自動的に拡大変換
d := b + x     // d の型は `f64` - `x` の値を自動的に拡大変換

文字列

name := 'Bob'
println(name.len)
println(name[0]) // B のバイトにインデックスでアクセス
println(name[1..3]) // 文字列 'ob' にスライスでアクセス
windows_newline := '\r\n' // C と同様に特殊な文字をエスケープ
assert windows_newline.len == 2

V での文字列は, 読み取り専用のバイト配列です。文字列データは UTF-8 エンコードされます。文字列の値はイミュータブルであり, 以下のように要素を変更できません。

mut s := 'hello 🌎'
s[0] = `H` // 不可能

error: cannot assign to s[i] since V strings are immutable

注意として, 文字列にインデックスでアクセスすると, rune ではなく byte が得られます。インデックスは Unicode コードポイントではなく文字列内のバイトに対応します。

文字リテラルは rune 型になります。これを表記するには、` で囲います。

rocket := `🚀`
assert 'aloha!'[0] == `a`

文字列を表すのに, シングルクォートとダブルクォートの両方が使用できます。一貫性のため, vfmt は文字列内にシングルクォートが含まれているかに関わらず, ダブルクォートをシングルクォートへ変換します。

生文字列は、r を前に付けます。生文字列はエスケープされません。

s := r'hello\nworld'
println(s) // "hello\nworld"

文字列は以下のように簡単に整数へ変換できます。

s := '42'
n := s.int() // 42

Rune

rune は Unicode 文字を表し、u32 のエイリアスです。Rune はこのように作成できます。

x := `🚀`

文字列は .runes() メソッドで Rune に変換できます。

hello := 'Hello World 👋'
hello_runes := hello.runes() // [`H`, `e`, `l`, `l`, `o`, ` `, `W`, `o`, `r`, `l`, `d`, ` `, `👋`]

文字列内挿

基本的な文字列内挿は, 以下のように変数名の前に $ を付けるだけの非常にシンプルな構文です。変数は文字列へ変換され, リテラルへ埋め込まれます。

name := 'Bob'
println('Hello, $name!') // Hello, Bob!

これは 'age = $user.age' のようなフィールドでも動作します。複雑な式が必要であれば, 'can register = ${user.age > 13}' のように ${} を使いましょう。

C の printf() のようなフォーマット指定子もサポートしています。fgx などが出力フォーマットで指定できます。ストレージのサイズを懸念した結果、このコンパイラに hdllu はありません。

x := 123.4567
println('x = ${x:4.2f}')
println('[${x:10}]') // 左端をスペースで埋める => [   123.457]
println('[${int(x):-10}]') // 右端をスペースで埋める => [123       ]
println('[${int(x):010}]') // 左端をゼロで埋める => [0000000123]

文字列の演算子

name := 'Bob'
bobby := name + 'by' // 文字列結合には + を使います
println(bobby) // "Bobby"
mut s := 'hello '
s += 'world' // 文字列の追加は `+=` です
println(s) // "hello world"

V のすべての演算子は、両辺が同じ型の値でなければなりません。以下のように、整数を文字列に連結することはできません。

age := 10
println('age = ' + age) // 不可能

error: infix expr: cannot use int (right expression) as string

以下のように age を文字列に変換する必要があります。

age := 11
println('age = ' + age.str())

以下のような文字列内挿でもできます (こちらが好ましい)。

age := 12
println('age = $age')

数値

a := 123

これは a に 123 の値を代入しています。既定で aint になります。

16 進数、2 進数, 8 進数の整数リテラルも使用できます。

a := 0x7B
b := 0b01111011
c := 0o173

これらはすべて同じ 123 の値を代入しています。型を指定していないため、すべて int 型になります。

V では _ を桁区切りに数を書くこともできます。

num := 1_000_000 // 1000000 と同じ
three := 0b0_11 // 0b11 と同じ
float_num := 3_122.55  // 3122.55 と同じ
hexa := 0xF_F // 255 と同じ
oct := 0o17_3 // 0o173 と同じ

異なる型の整数にしたい場合は、キャストを使うことでできます。

a := i64(123)
b := byte(42)
c := i16(12345)

浮動小数点数の代入も同様に動作します。

f := 1.0
f1 := f64(3.14)
f2 := f32(3.14)

この型を明示しない場合、浮動小数点数リテラルは既定で f64 型になります。

浮動小数点数リテラルは以下のように 10 のべき乗で宣言することもできます。

f0 := 42e1 // 420
f1 := 123e-2 // 1.23
f2 := 456e+2 // 45600

配列

基本的な配列の概念

配列は、同じ型のデータ要素の集まりです。角括弧で囲まれた要素のリストで表されます。要素にアクセスするには、配列変数に角括弧で囲んだ添字(0 始まり)を付ける必要があります。

mut nums := [1, 2, 3]
println(nums) // `[1, 2, 3]`
println(nums[0]) // `1`
println(nums[1]) // `2`
nums[1] = 5
println(nums) // `[1, 5, 3]`

配列のプロパティ

配列にはその「サイズ」を制御する以下 2 つのプロパティがあります。

  • len: length - 配列内で事前に確保して初期化された要素の数
  • cap: capacity - 要素のために確保しているが、未初期化で要素としてカウントされていないメモリ空間の量。配列は、再確保せずにこのサイズまで伸長できます。通常、V はこのプロパティを自動処理しますが、ユーザによる手動の最適化 (以下 を参照) をする場合もあります 。
mut nums := [1, 2, 3]
println(nums.len) // "3"
println(nums.cap) // "3" 以上
nums = [] // 配列は空
println(nums.len) // "0"

注意として、これらのプロパティは読み取り専用のフィールドでありユーザは変更できません。

配列の初期化

基本的な初期化の構文は上記の通りです。配列の型は、その最初の要素で決まります。

  • [1, 2, 3] は int の配列 ([]int)。
  • ['a', 'b'] は string の配列 ([]string)。

ユーザは、[byte(16), 32, 64, 128] のように最初の要素の型を明示的に指定できます。V の配列はすべての要素が同じ型でなければなりません。すなわち、[1, 'a'] のようなコードはコンパイルできません。

上記の構文はよくある要素数が少ない配列では問題ありませんが、非常に大きな配列や空の配列のために 2 つ目の初期化構文があります。

mut a := []int{len: 10000, cap: 30000, init: 3}

これは 10000 個の int 要素の配列が作成され、すべてが 3 で初期化されます。メモリスペースは 30000 要素ぶん確保されます。パラメータの lencapinit は任意です。len のデフォルトは 0 で、init は要素型のデフォルトの初期値になります (数値型は 0string''、など)。ランタイムシステムは、容量が len より小さくならないようにします (より小さい値が明示的に指定されていても)。

arr := []int{len: 5, init: -1}
// `arr == [-1, -1, -1, -1, -1]`, arr.cap == 5

// 空の配列の宣言:
users := []int{}

容量を指定することで再確保の回数が減り、挿入時のパフォーマンスが向上します。

mut numbers := []int{ cap: 1000 }
println(numbers.len) // 0
// この要素の追加では再確保しない
for i in 0 .. 1000 {
    numbers << i
}

注: 上記のコードでは 範囲 for 文と push 演算子 (<<) を使用しています。

配列の型

配列は以下の型にできます。

定義例
数値 []int []i64
文字列 []string
Rune []rune
真偽値 []bool
配列 [][]int
構造体 []MyStructName
チャンネル []chan f64
関数 []MyFunctionType []fn (int) bool
Interface []MyInterfaceName
直和型 []MySumTypeName
ジェネリック型 []T
辞書配列 []map[string]f64
列挙体 []MyEnumType
エイリアス []MyAliasTypeName
スレッド []thread int
参照 []&f64
共有 []shared MyStructType
コード例

この例では、構造体と直和型を使用して、さまざまな種類のデータ要素 (点、線など)を処理できる配列を作成しています。

struct Point {
    x int
    y int
}

struct Line {
    p1 Point
    p2 Point
}

type ObjectSumType = Line | Point

mut object_list := []ObjectSumType{}
object_list << Point{1, 1}
object_list << Line{
    p1: Point{3, 3}
    p2: Point{4, 4}
}
dump(object_list)
/*
object_list: [ObjectSumType(Point{
    x: 1
    y: 1
}), ObjectSumType(Line{
    p1: Point{
        x: 3
        y: 3
    }
    p2: Point{
        x: 4
        y: 4
    }
})]
*/

多次元配列

配列は 1 次元よりも多くできます。

二次元配列の例:

mut a := [][]int{len:2, init: []int{len:3}}
a[0][1] = 2
println(a) // [[0, 2, 0], [0, 0, 0]]

三次元配列の例:

mut a := [][][]int{len:2, init: [][]int{len:3, init: []int{len:2}}}
a[0][1][1] = 2
println(a) // [[[0, 0], [0, 2], [0, 0]], [[0, 0], [0, 0], [0, 0]]]

配列の操作

push 演算子 << を使って、配列の最後に要素を追加できます。また、配列全体も追加できます。

mut nums := [1, 2, 3]
nums << 4
println(nums) // "[1, 2, 3, 4]"
// 配列の追加
nums << [5, 6, 7]
println(nums) // "[1, 2, 3, 4, 5, 6, 7]"
mut names := ['John']
names << 'Peter'
names << 'Sam'
// names << 10  <-- これはコンパイルできません。`names` 文字列の配列です。

val in array は、配列が val を含む場合に true を返します。in 演算子 をご参照ください。

names := ['John', 'Peter', 'Sam']
println(names.len) // "3"
println('Alex' in names) // "false"

配列のメソッド

すべての配列は println(arr) で簡単に出力でき、s := arr.str() で文字列に変換できます。

配列からのデータのコピーには .clone() を使います。

nums := [1, 2, 3]
nums_copy := nums.clone()

配列は以下のように .filter().map() メソッドで効果的にフィルター、マップできます。

nums := [1, 2, 3, 4, 5, 6]
even := nums.filter(it % 2 == 0)
println(even) // [2, 4, 6]
// filter には無名関数が使える
even_fn := nums.filter(fn (x int) bool {
    return x % 2 == 0
})
println(even_fn)
words := ['hello', 'world']
upper := words.map(it.to_upper())
println(upper) // ['HELLO', 'WORLD']
// map にも無名関数が使える
upper_fn := words.map(fn (w string) string {
    return w.to_upper()
})
println(upper_fn) // ['HELLO', 'WORLD']

itfilter / map メソッド内で現在処理している要素を表す組み込み変数です。

さらに .any().all() を使えば、要素が条件を満たすかどうか簡単にテストできます。

nums := [1, 2, 3]
println(nums.any(it == 2)) // true
println(nums.all(it >= 2)) // false

配列には他にも以下のような組み込みメソッドがあります。

  • b := a.repeat(n) n 個ぶんの a の要素を結合する
  • a.insert(i, val) 新しい要素 val をインデックス i に挿入して以降のすべての要素を後ろに動かす
  • a.insert(i, [3, 4, 5]) 複数の要素を挿入する
  • a.prepend(val) 値を最初に挿入する、a.insert(0, val) と等価
  • a.prepend(arr) 配列 arr の要素を最初に挿入する
  • a.trim(new_len) 長さを切り詰める (new_length < a.len である場合のみ、さもなくば何もしない)
  • a.clear() 配列を空にする (cap は変わらない、a.trim(0) と等価)
  • a.delete_many(start, size) インデックス start から始まる連続した size 個の要素を削除する - 再確保を起こす
  • a.delete(index) a.delete_many(index, 1) と等価
  • v := a.first() v := a[0] と等価
  • v := a.last() v := a[a.len - 1] と等価
  • v := a.pop() 最後の要素を取得して配列から取り除く
  • a.delete_last() 配列から最後の要素を取り除く
  • b := a.reverse() ba の要素の順番を逆にしたものを格納する
  • a.reverse_in_place() a の要素の順番を逆にする
  • a.join(joiner) joiner 文字列を区切りにして、文字列の配列を文字列へと連結する。

配列のソート

大抵の配列のソートは非常にシンプルで直感的です。特殊変数 ab でソートの条件をカスタマイズできます。

mut numbers := [1, 3, 2]
numbers.sort() // 1, 2, 3
numbers.sort(a > b) // 3, 2, 1
struct User {
    age  int
    name string
}

mut users := [User{21, 'Bob'}, User{20, 'Zarkon'}, User{25, 'Alice'}]
users.sort(a.age < b.age) // User.age int フィールドでソート
users.sort(a.name > b.name) // User.name string フィールドで逆順にソート

V は、配列メソッド sort_with_compare によるカスタムソートもサポートしています。これにはソート順を定義する比較関数が必要です。カスタムのソートルールで複数のフィールドを同時にソートするのに便利です。以下のコードでは、配列を名前の昇順、年齢の降順でソートしています。

struct User {
    age  int
    name string
}

mut users := [User{21, 'Bob'}, User{65, 'Bob'}, User{25, 'Alice'}]

custom_sort_fn := fn (a &User, b &User) int {
    // a が b より前なら -1 を返す
    // 療法が同じ順番なら 0 を返す
    // b が a より前なら 1 を返す
    if a.name == b.name {
        if a.age < b.age {
            return 1
        }
        if a.age > b.age {
            return -1
        }
        return 0
    }
    if a.name < b.name {
        return -1
    } else if a.name > b.name {
        return 1
    }
    return 0
}
users.sort_with_compare(custom_sort_fn)

配列のスライス

スライスは部分的な配列です。スライスは .. 演算子で区切られた2つのインデックスの間にあるすべての要素を表します。右辺のインデックスは左辺のインデックス以上でなければなりません。

右辺のインデックスがない場合は配列の長さになります。左辺のインデックスが存在しない場合は 0 になります。

nums := [0, 10, 20, 30, 40]
println(nums[1..4]) // [10, 20, 30]
println(nums[..4]) // [0, 10, 20, 30]
println(nums[1..]) // [10, 20, 30, 40]

すべての配列操作はスライスに対しても実行できます。スライスは同じ型の配列に追加できます。

array_1 := [3, 5, 4, 7, 6]
mut array_2 := [0, 1]
array_2 << array_1[..3]
println(array_2) // [0, 1, 3, 5, 4]

スライスは、元の配列の容量や長さに関わらず常に最小の容量 cap == len上記の cap を参照)で作成されます。その結果、サイズが大きくなったときはすぐに再確保され、別のメモリ位置にコピーされるので、親配列から独立したものになります(増加時にコピー)。特に、スライスに要素をプッシュしても親配列は変更されません。

mut a := [0, 1, 2, 3, 4, 5]
mut b := a[2..4]
b[0] = 7 // `b[0]` は `a[2]` を参照する
println(a) // `[0, 1, 7, 3, 4, 5]`
b << 9
// `b` は再確保されて `a` に依存しなくなる
println(a) // `[0, 1, 7, 3, 4, 5]` - no change
println(b) // `[7, 3, 9]`

元の配列に追加することで、そのスライスから独立させることができる場合とできない場合があります。その挙動は親の容量に依存しており、予測可能です。

mut a := []int{len: 5, cap: 6, init: 2}
mut b := a[1..4]
a << 3
// 再確保なし - `cap` に一致
b[2] = 13 // `a[3]` を変更
a << 4
// a が再確保され `b` は依存しなくなる (`cap` を超える)
b[1] = 3 // `a` は変わらない
println(a) // `[2, 2, 2, 13, 2, 3, 4]`
println(b) // `[2, 3, 13]`

固定長配列

V は固定長配列もサポートしています。通常の配列とは異なり、その長さは一定です。配列に要素を追加したり縮小したりできず、その要素を変更することしかできません。

しかし、固定長配列の要素へのアクセスは効率的で、通常の配列よりも少ないメモリしか必要としません。

ほとんどのメソッドは通常の配列で動作するように定義されており、固定長配列では動作しません。固定長配列をスライスすることで通常の配列に変換できます。

mut fnums := [3]int{} // fnums は 3 要素の固定長配列です。
fnums[0] = 1
fnums[1] = 10
fnums[2] = 100
println(fnums) // => [1, 10, 100]
println(typeof(fnums).name) // => [3]int

fnums2 := [1, 10, 100]! // 同じことをする短縮初期化構文 (構文は変わる可能性あり)

anums := fnums[0..fnums.len]
println(anums) // => [1, 10, 100]
println(typeof(anums).name) // => []int

注意として、スライスする際に固定長配列のデータが新たに作成した通常の配列へコピーされます。

辞書配列

mut m := map[string]int // `string` をキーに `int` を格納する辞書配列
m['one'] = 1
m['two'] = 2
println(m['one']) // "1"  
println(m['bad_key']) // "0"  
println('bad_key' in m) // `in` を使ってそのようなキーが存在するかどうか検出する
m.delete('two')

辞書配列のキーは任意の型を保持でき、以下の場合は int にしています。

以下の短縮構文で辞書配列全体を初期化することもできます。

numbers := {
    1: 'one'
    2: 'two'
}
println(numbers)

指定のキーが見つからなかった場合、デフォルトでゼロ値が返されます。

sm := {
    'abc': 'xyz'
}
val := sm['bad_key']
println(val) // ''


intm := {
1: 1234
2: 5678
}
s := intm[3]
println(s) // 0
`

また、or {} ブロックを使用することで存在しなかったキーのハンドリングも可能です。

mm := map[string]int{}
val := mm['bad_key'] or { panic('key not found') }

この任意の確認は、配列でも同様に適用されます。

arr := [1, 2, 3]
large_index := 999
val := arr[large_index] or { panic('out of bounds') }

モジュールのインポート

モジュールの作り方は モジュール を参照してください。

モジュールはキーワード import を使用してインポートすることができます。

import os

fn main() {
    // stdin からテキストを読み取る
    name := os.input('Enter your name: ')
    println('Hello, $name!')
}

このプログラムでは、input 関数のような os モジュールの公開された定義を使用することができます。一般的なモジュールとその公開されたシンボルのリストについては、標準ライブラリ のドキュメントを参照してください。

デフォルトでは、外部関数を呼び出すたびにモジュールを前に指定しなければなりません。最初は冗長に見えるかもしれませんが、コードがずっと読みやすくなり、どのモジュールのどの関数が呼び出されているのか理解しやすくなります。これは大規模なコードベースでは特に有用です。

Go のように、モジュールの循環インポートはできません。

選択インポート

関数や型は他のモジュールから直接インポートできます。

import os { input }

fn main() {
    // 標準入力からテキストを読む
    name := input('Enter your name: ')
    println('Hello, $name!')
}

注意: これにより、モジュールも同様にインポートされます。また、定数でこれはできません。定数は常にプレフィックスを付けなければなりません。

複数の特定のシンボルを一度にインポートすることもできます。

import os { input, user_os }

name := input('Enter your name: ')
println('Name: $name')
os := user_os()
println('Your OS is ${os}.')

モジュールインポートの別名

as キーワードを用いてインポートしたモジュール名に別名をつけることができます。

注: この例は mymod/sha256/v を作成していない限りコンパイルできません。

import crypto.sha256
import mymod.sha256 as mysha256

fn main() {
    v_hash := sha256.sum('hi'.bytes()).hex()
    my_hash := mysha256.sum('hi'.bytes()).hex()
    assert my_hash == v_hash
}

関数や型のインポートに別名を付けることはできませんが、型の再宣言は できます

import time

type MyTime = time.Time

fn (mut t MyTime) century() int {
    return 1 + t.year % 100
}

fn main() {
    my_time := MyTime{
        year: 2020,
        month: 12,
        day: 25
    }
    println(time.new_time(my_time).utc_string())
    println('Century: ${my_time.century()}')
}

文と式

If

a := 10
b := 20
if a < b {
    println('$a < $b')
} else if a > b {
    println('$a > $b')
} else {
    println('$a == $b')
}

if 文は素直で単純かつ他のほとんどの言語と同じです。
他の C 系の言語とは異なり、条件式は括弧で囲まず、波括弧は常に必要です。

if は以下のように式としても使用できます。

num := 777
s := if num % 2 == 0 { 'even' } else { 'odd' }
println(s)
// "odd"

型チェックとキャスト

直和型の現在の型がどうなっているかは is で、否定は !is で確認できます。

これは、以下のように if の中でも可能です。

struct Abc {
    val string
}
struct Xyz {
    foo string
}

type Alphabet = Abc | Xyz

x := Alphabet(Abc{'test'}) // 直和型
if x is Abc {
    // x は自動で Abc にキャストをされここで使える
    println(x)
}
if x !is Abc {
    println('Not Abc')
}

もしくは、以下のように match を使います。

match x {
    Abc {
        // x は自動で Abc へキャストされてここで使える
        println(x)
    }
    Xyz {
        // x は自動で Xyz へキャストされてここで使える
        println(x)
    }
}

これは構造体のフィールドでも動作します。

struct MyStruct {
    x int
}

struct MyStruct2 {
    y string
}

type MySumType = MyStruct | MyStruct2

struct Abc {
    bar MySumType
}

x := Abc{
    bar: MyStruct{123} // MyStruct は自動的に MySumType 型へ変換される
}
if x.bar is MyStruct {
    // x.bar は自動でキャストされる
    println(x.bar)
}
match x.bar {
    MyStruct {
        // x.bar は自動でキャストされる
        println(x.bar)
    }
    else {}
}

可変な変数は変化することがあり、安全にキャストできません。しかし、可変であっても型キャストが必要な場合もあります。この場合、開発者は式に mut キーワードを付けて、コンパイラに自分がしていることを分かっていると伝える必要があります。

これは以下のような具合で動作します。

mut x := MySumType(MyStruct{123})
if mut x is MyStruct {
    // x が可変であっても MyStruct へキャストされる
    // mut キーワードがなければ動作しない
    println(x)
}
// match も同様
match mut x {
    MyStruct {
        // x が可変であっても MyStruct へキャストされる
        // mut キーワードがなければ動作しない
        println(x)
    }
}

In 演算子

in はある配列にある要素が含まれるかどうかを確かめることができます。

nums := [1, 2, 3]
println(1 in nums) // true
println(4 !in nums) // true
m := {
    'one': 1
    'two': 2
}
println('one' in m) // true
println('three' !in m) // true

さらに、以下のように真偽値の式をより明瞭かつコンパクトに書くのに役立ちます。

enum Token {
    plus
    minus
    div
    mult
}

struct Parser {
    token Token
}

parser := Parser{}
if parser.token == .plus || parser.token == .minus || parser.token == .div || parser.token == .mult {
    ... 
} 
if parser.token in [.plus, .minus, .div, .mult] {
    ... 
}

V はこういった式を最適化します。上に挙げた両方の if 文は同じ機械語を生成し、配列は作成されません。

For ループ

V でのループ構造は for のみで、いくつかの形式があります。

for/in

これは最も一般的な形式です。配列、辞書配列、数値の範囲で使用できます。

配列 for
numbers := [1, 2, 3, 4, 5]
for num in numbers {
    println(num)
}
names := ['Sam', 'Peter']
for i, name in names {
    println('$i) $name') 
    // 出力: 0) Sam
    //     1) Peter
}

for value in arr ループは配列の要素を取っていくのに用いられます。インデックスが必要な場合、もう一つの書き方として for index, value in が使用できます。

注意として、value は読み取り専用です。ループで配列を変更する必要がある場合、インデックスを用います。

mut numbers := [0, 1, 2]
for mut num in numbers {
    num++
}
println(numbers) // [1, 2, 3]

識別子がアンダースコア一つのときは、無視されます。

カスタムイテレータ

Option を返す next メソッドを実装した型は、for ループで繰り返せます。

struct SquareIterator {
    arr []int
mut:
    idx int
}

fn (mut iter SquareIterator) next() ?int {
    if iter.idx >= iter.arr.len {
        return error('')
    }
    defer {
        iter.idx++
    }
    return iter.arr[iter.idx] * iter.arr[iter.idx]
}

nums := [1, 2, 3, 4, 5]
iter := SquareIterator{
    arr: nums
}
for squared in iter {
    println(squared)
}

上記のコードは以下を出力します。

1
4
9
16
25
辞書配列 for
m := {'one':1, 'two':2}
for key, value in m {
    println("$key -> $value")  // 出力: one -> 1
}                              //      two -> 2

キーや値を無視するにはアンダースコアを識別子に用いることでできます。

m := {
    'one': 1
    'two': 2
}
// キーで繰り返し
for key, _ in m {
    println(key)
    // Output: one
    //         two
}
// 値で繰り返し
for _, value in m {
    println(value)
    // Output: 1
    //         2
}
範囲 for
// '01234' と出力する
for i in 0..5 {
    print(i)
}

low..high末尾を含まない 範囲を意味します。つまり low 以上 high 未満で high を含まない すべての整数を表します。

条件 for
mut sum := 0
mut i := 0
for i <= 100 {
    sum += i
    i++
}
println(sum) // "5050"

このループの書き方は他の言語での while ループと同様です。このループは条件式が false に評価されると繰り返しを止めます。同じく、条件式は括弧で囲まず、波括弧は常に必要です。

危険な for

mut sum := 0
for {
    num++
    if num >= 10 {
        break 
    }
}
println(num) // "10"

条件式は省略でき、これは無限ループになります。

C for

for i := 0; i < 10; i++ {
    // 6 は出力しない
    if i == 6 {
        continue
    }
    println(i)
}

最後に、C スタイルの for ループもあります。これは while より安全な書き方です。なぜなら、後者の場合はカウンターの更新を忘れて無限ループに陥りやすいからです。

この imut をつけて宣言する必要はありません。定義したとき常にミュータブルになります。

ラベル付き breakcontinue

breakcontinue はデフォルトで直近の for ループを制御します。外側の for ループを示すには, そのラベル名を breakcontinue に続けて書きます。

outer: for i := 4; true; i++ {
    println(i)
    for {
        if i < 7 {
            continue outer
        } else {
            break outer
        }
    }
}

ラベルは外側のループの前に付ける必要があります。上記コードの出力は以下のとおりです。

4
5
6
7

Match

os := 'windows'
print('V is running on ')
match os {
    'darwin' { println('macOS.') }
    'linux'  { println('Linux.') }
    else     { println(os) }
}

match 文は if - else 文の羅列を短く書く手段です。条件式の値と等しい最初の節が見つかると、続く式が評価されます。else 節は他に一致する節がない場合に評価されます。

number := 2
s := match number {
    1 { 'one' }
    2 { 'two' }
    else { 'many' }
}

match 式は各節の最後の式を返します。

enum Color {
    red
    blue
    green
}

fn is_red_or_blue(c Color) bool {
    return match c {
        .red, .blue { true } // カンマで分けて複数の値を検知できる
        .green  { false }
    }
}

match 文は .種類 という省略構文で enum の種類を分岐することにも使用できます。

c := `v`
typ := match c {
    `0`...`9` { 'digit' }
    `A`...`Z` { 'uppercase' }
    `a`...`z` { 'lowercase' }
    else      { 'other' }
}
println(typ)
// 'lowercase'

match のパターンに範囲を使用することもできます。値が節のその範囲に収まれば、その節を実行します。

注意として、範囲には .. (点 2 つ) ではなく ... (点 3 つ) を使用してください。これは含まない (.. の範囲) のと違い、範囲が最後の要素を 含む ようになります。..match の節に使用するとエラーが発生します。

注: match 式は for ループや if 文の中では使用できません。

defer

defer 文は、これが書かれているの関数が戻るまでその文にあたるブロックの実行を引き伸ばします。

fn read_log() {
    f := os.open('log.txt')
    defer { f.close() }
    // ...
    if !ok {
        // defer 文がここで呼び出され、ファイルが閉じられる
        return
    }
    // ...
    // defer 文がここで呼び出され、ファイルが閉じられる
}

関数が値を返す場合は、return式が評価された後にdeferブロックが実行されます。

import os

enum State {
    normal
    write_log
    return_error
}

// ログファイルを書き込んで、書き込んだバイト数を返す
fn write_log(s State) ?int {
    mut f := os.create('log.txt') ?
    defer {
        f.close()
    }
    if s == .write_log {
        // `f.close()` は、`f.write()` が実行された後、
        // `write_log()` が最終的に書き込まれたバイト数を
        // `main()` へ返す前に呼び出されます
        return f.writeln('This is a log file')
    } else if s == .return_error {
        // ファイルは `error()` 関数が返された後に
        // クローズされるので、エラーメッセージにはオープン
        // と表示されます
        return error('nothing written; file open: $f.is_opened')
    }
    // ここでもファイルは閉じられます
    return 0
}

fn main() {
    n := write_log(.return_error) or {
        println('Error: $err')
        0
    }
    println('$n bytes written')
}

構造体

struct Point {
    x int
    y int
}

p := Point{
    x: 10
    y: 20
}
println(p.x) // 構造体のフィールドはドットでアクセスできます
// 3 つ以下のフィールドの構造体を初期化する、もう一つの構文
p = Point{10, 20}
assert p.x == 10

ヒープの構造体

構造体はスタック上に確保されます。ヒープ上に確保する場合は、以下のように & 接頭子を使用してその参照を取得します。

struct Point {
    x int
    y int
}
p := &Point{10, 10}
// 参照にもフィールドにアクセスする構文がある  
println(p.x)

p の型は &Point です。これは Point への 参照 です。参照は Go のポインタや C++ の参照と似ています。

埋め込み構造体

V にサブクラス化はありませんが、埋め込み構造体はあります。

struct Widget {
mut:
    x int
    y int
}

struct Button {
    Widget
    title string
}

mut button := Button{
    title: 'Click me'
}
button.x = 3

埋め込まずに widget というフィールドにした場合はこのようにすることになります。

button.widget.x = 3

フィールドのデフォルト値

struct Foo {
    n   int      // n は 0 がデフォルト値
    s   string   // s は '' がデフォルト値
    a   []int    // a は `[]int{}` がデフォルト値
    pos int = -1 // カスタムデフォルト値
}

構造体の全フィールドは、構造体の作成時にデフォルトでゼロになります。配列と辞書配列のフィールドはメモリが確保されます。

カスタムのデフォルト値を定義することも可能です。

必須フィールド

struct Foo {
    n int [required]
}

構造体のフィールドに [required] 属性を付けることで、その構造体のインスタンスを初期化する時にそのフィールドが必要であると V に伝えることが出来ます。

以下の例は、フィールド n が明示的に初期化されていないのでコンパイルできません。

_ = Foo{}

短縮構造体リテラル構文

struct Point{
    x int
    y int
}
mut p := Point{
    x: 10
    y: 20
}
// 構造体名が分かっているので省略できます
p = Point{
    x: 30
    y: 4
}
assert p.y == 4
//
// 配列: 最初の要素が配列の型を決定する
points := [Point{10, 20}, Point{20, 30}, Point{40, 50}]
println(points) // [Point{x: 10, y: 20}, Point{x: 20, y: 30}, Point{x: 40,y: 50}]

構造体名の省略は、構造体リテラルを返す場合や、関数の引数として構造体を渡す場合に機能します。

末尾構造体リテラル引数
struct ButtonConfig {
    text        string
    is_disabled bool
    width       int = 70
    height      int = 20
}

fn new_button(c ButtonConfig) &Button {
    return &Button{
        width: c.width
        height: c.height
        text: c.text
    }
}

button := new_button(text:'Click me', width:100)
// height を指定していないので、デフォルト値になります
assert button.height == 20

ご覧の通り、構造体名や波括弧は省略できます。以下のようにする必要はありません。

new_button(ButtonConfig{text:'Click me', width:100})

これは、構造体を最後の引数に取る関数でのみ動作します。

アクセス指定子

構造体のフィールドはデフォルトで非公開かつイミュータブルです (構造体もイミュータブルです)。これらのアクセス指定は pubmut で変更できます。以下のようにあわせて 5 つの選択肢があります。

struct Foo {
    a int     // 非公開 イミュータブル (デフォルト) 
mut: 
    b int     // 非公開 ミュータブル
    c int     // (同じアクセス指定で複数のフィールドを並べられる)
pub: 
    d int     // 公開 イミュータブル (読み取り専用) 
pub mut: 
    e int     // 公開 親モジュールでのみミュータブル 
__global: 
    f int     // 公開かつ親モジュールの内側でも外側でもミュータブル 
}             // (利用を推奨していないので、'global' キーワードは __ で始まります) 

例として、組み込みモジュール内では string 型がこのように定義されています。

struct string {
    str &byte
pub:
    len int
}

この定義から string がイミュータブル型であることが簡単にわかります。文字列データのバイトポインタは builtin の外部からアクセスできません。len フィールドは公開されていますが、変更できません。

fn main() {
    str := 'hello' 
    len := str.len // OK  
    str.len++      // コンパイルエラー
} 

V では、公開読み取り専用フィールドの定義が非常に簡単で、プロパティのゲッターやセッターが必要がないということです。

メソッド

struct User {
    age int 
} 

fn (u User) can_register() bool {
    return u.age > 16 
} 

user := User{
    age: 10
}
println(user.can_register()) // "false"
user2 := User{
    age: 20
}
println(user2.can_register()) // "true"

V にはクラスがありません。しかし、型にメソッドを定義できます。メソッドは特殊なレシーバー引数のある関数です。レシーバーの引数リストは fn キーワードとメソッド名の間に出てきます。

この例では、can_register メソッドには User 型の u と命名されたレシーバーがあります。慣習として selfthis のようなレシーバー名は使わずに、短めで、一文字長の名前が好まれます。

共用体

Just like structs, unions support embedding.

struct Rgba32_Component {
    r byte
    g byte
    b byte
    a byte
}

union Rgba32 {
    Rgba32_Component
    value u32
}

clr1 := Rgba32{
    value: 0x008811FF
}

clr2 := Rgba32{
    Rgba32_Component: Rgba32_Component{
        a: 128
    }
}

sz := sizeof(Rgba32)
unsafe {
    println('Size: ${sz}B,clr1.b: $clr1.b,clr2.b: $clr2.b')
}

出力: Size: 4B, clr1.b: 136, clr2.b: 0

共用体のメンバーへのアクセスは unsafe ブロック内で行なければなりません。

注意として、埋め込み構造体の引数は必ずしも列挙した順番通りに格納されるわけではありません。

関数 2

既定で純粋関数

V の関数はデフォルトで純粋です。これは戻り値は引数によってのみ決定されるという意味で、その評価に副作用はありません。

これはグローバル変数がないことと、関数の引数は 参照 を渡してもデフォルトでイミュータブルであることによって実現しています。

しかし V は純粋関数型言語ではありません。

グローバル変数を有効にするコンパイラフラグ (--enable-globals) がありますが、これはカーネルやドライバといった低水準アプリケーションを想定しています。

可変引数

以下のように mut キーワードを使うことで、関数の引数を変更できます。

struct User {
    name string
mut:
    is_registered bool 
} 

fn (mut u User) register() {
    u.is_registered = true 
} 

mut user := User{} 
println(user.is_registered) // "false"  
user.register() 
println(user.is_registered) // "true"

この例では、レシーバー (単なる第一引数) がミュータブルにされているので、register()user オブジェクトを変更できます。以下のレシーバーの無い関数でも同様に動作します。

fn multiply_by_2(mut arr []int) {
    for i in 0..arr.len {
        arr[i] *= 2
    }
}

mut nums := [1, 2, 3]
multiply_by_2(mut nums)
println(nums) // "[2, 4, 6]"

注意として、mutnums の前に追加してこの関数を呼び出す必要があります。これは呼び出す関数が値を変更することを明確にします。

引数を変更するよりは戻り値を返すほうが望ましいです。引数の変更は、アプリケーションのパーフォーマンス的に重要な箇所でメモリ確保やコピーを削減するためにのみされるべきです。

この理由から、V ではプリミティブ型 (整数など) の引数を変更できません。配列や辞書配列のような、より複雑な型のみが変更できます。

register(mut user) よりは user.register()user = register(user) を使用してください。

構造体の更新構文

V でオブジェクトの変更版を返すことは簡単です。

struct User {
    name          string
    age           int
    is_registered bool
}

fn register(u User) User {
    return {
        u |
        is_registered: true
    }
}

mut user := User{
    name: 'abc'
    age: 23
}
user = register(user)
println(user)

可変長引数

fn sum(a ...int) int {
    mut total := 0
    for x in a {
        total += x
    }
    return total
}
println(sum())     // 出力: 0
println(sum(1))    //      1
println(sum(2, 3)) //      5
// 配列展開の使用例
a := [2, 3, 4]
println(sum(...a)) // <-- ... 接頭子をここで使います。出力: 9
b := [5, 6, 7]
println(sum(...b)) // 出力: 18

無名関数と高階関数

fn sqr(n int) int {
    return n * n
}

fn cube(n int) int {
    return n * n * n
}

fn run(value int, op fn (int) int) int {
    return op(value)
}

fn main() {
    // 関数は他の関数に渡せる
    println(run(5, sqr)) // "25"
    // 関数の中で無名の関数を宣言できる
    double_fn := fn (n int) int {
        return n + n
    }
    println(run(5, double_fn)) // "10"
    // 変数に代入しなくても、関数を渡したりできる
    res := run(5, fn (n int) int {
        return n + n
    })
    println(res) // "10"
    // 関数の配列/辞書配列もできる
    fns := [sqr, cube]
    println(fns[0](10)) // "100"
    fns_map := {
        'sqr':  sqr
        'cube': cube
    }
    println(fns_map['cube'](2)) // "8"
}

参照

struct Foo{}

fn (foo Foo) bar_method() {
    // ...
}

fn bar_function(foo Foo) {
    // ...
}

上の例での foo のように引数がイミュータブルな場合、V は値か参照で値渡しできます。この決定はコンパイラによって行われ、開発者がそのことを考慮する必要はありません。

構造体を値で渡すべきか参照で渡すべきか覚えておく必要はもうないのです。

常に参照渡しすることを確実にするには、以下のように & を付けます。

struct Foo {
    abc int
}

fn (foo &Foo) bar() {
    println(foo.abc)
}

これでも foo はイミュータブルで変更されません。そのためには、(mut foo Foo) とする必要があります。

一般的に、V の参照は Go のポインタや C++ の参照と似ています。例えば、木構造の定義はこのようになります。

struct Node<T> {
    val   T
    left  &Node<T>
    right &Node<T>
}

定数

const (
    pi    = 3.14
    world = '世界'
) 

println(pi)
println(world)

定数は const キーワードで宣言されます。これらはモジュールレベル (関数の外側) で定義できます。定数は変更できません。

const e = 2.71828

V の定数はほとんどの言語よりも柔軟です。以下の複雑な値も代入できます。

struct Color {
    r int
    g int
    b int
}

fn rgb(r int, g int, b int) Color {
    return Color{
        r: r
        g: g
        b: b
    }
}

const (
    numbers = [1, 2, 3]
    red     = Color{
        r: 255
        g: 0
        b: 0
    }
    // 関数呼び出しはコンパイル時に評価される
    blue    = rgb(0, 0, 255)
)

println(numbers)
println(red)
println(blue)

* WIP - 現在、関数呼び出しをしている場合はプログラム起動時に評価されます

グローバル変数が許可されていないので、これは非常に役立ちます。

モジュール

定数は以下のように pub const で公開できます。

module mymodule

pub const golden_ratio = 1.61803

fn calc() {
    println(mymodule.golden_ratio)
}

pub キーワードは const キーワードの前にのみ付加でき、const ( ) ブロック内では使用できません。

main モジュールの外にあるすべての定数はモジュール名をその前に付ける必要があります。

モジュール名を前に付けることが必須

定数の名前をつけるときは、snake_case を使わなければなりません。定数はローカル変数と区別するため、定数のフルパスを指定する必要があります。たとえば、PI 定数にアクセスするには、完全な math.pi という名前を math モジュールの外と中の両方で使用する必要があります。この制限は main モジュール (fn main() を含むモジュール) においてのみ緩和され、そこでは main.numbers ではなく、numbers のように定義された定数の非限定的な名前を使用できます。

vfmt はこのルールを管理しており、math モジュール内で println(pi) と入力すると、vfmt は自動的に println(math.pi) に更新します。

組み込み関数

println のような関数は組み込みです。以下は組み込み関数の完全なリストです。

fn print(s string) // 標準出力に出力します
fn println(s string) // 標準出力に出力して改行します

fn eprint(s string) // print() と同じですが、標準エラー出力を使います
fn eprintln(s string) // println() と同じですが、標準エラー出力を使います

fn exit(code int) // 指定のエラーコードでプログラムを終了します
fn panic(s string) // メッセージとバックトレースを標準エラー出力に出力して、エラーコード 1 でプログラムを終了します
fn print_backtrace() // バックトレースを標準エラー出力に出力します

println はシンプルかつ強力な組み込み関数です。これは、文字列、数値、配列、辞書配列、構造体など、なんでも出力できます。

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}"

型のカスタム出力

自分の型に対してカスタムの出力値を定義したい場合は、.str() string メソッドを定義するだけです。

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)

モジュール

フォルダのルートにあるすべてのファイルは、同じモジュールの一部です。単純なプログラムではモジュール名を指定する必要はありませんが、その場合はデフォルトで「main」となります。

V はとてもモジュール方式な言語です。再利用可能なモジュールを作成することは、推奨されており、非常に簡単です。新しいモジュールを作成するには、モジュールの名前のディレクトリを作成してコードを書いた .v ファイルを入れます。

cd ~/code/modules
mkdir mymodule
vim mymodule/myfile.v
myfile.v
module mymodule

// 関数をエクスポートするには `pub` を使用します
pub fn say_hi() {
    println('hello from mymodule!')
}

これで mymodule をコード内で使えるようになりました。

import mymodule

fn main() { 
    mymodule.say_hi()
}
  • モジュールの名前は 10 文字以下くらいに短くするべきです。
  • 循環インポートは許可されていません。
  • モジュール内に .v ファイルをいくらでも作成できます。
  • 今現在はモジュールをどこにでも作成できます。
  • すべてのモジュールは静的に単一の実行形式にコンパイルされます。

init 関数

インポートされた場合に何らかのセットアップ/初期化コードを自動的に呼び出してほしい場合、以下のような init 関数を利用できます。

fn init() {
    // ここにセットアップコード
}

init 関数は公開できません。自動的に呼び出されます。これは C ライブラリの初期化に役立ちます。

パッケージの管理

概要:

v [モジュールのオプション] [引数]

モジュールのオプション:

   install           VPM からモジュールをインストールする。
   remove            VPM からインストールしたモジュールを取り除く。
   search            VPM からモジュールを検索する。
   update            VPM からインストールしたモジュールを更新する。
   upgrade           すべての outdated なモジュールを更新する。
   list              すべてのインストールしたモジュールを列挙する。
   outdated          インストールしたモジュールのうち更新が必要なものを表示する。

詳細:

他人が作成したモジュールを VPM でインストールすることもできます。

v install [module]

例:

v install ui

以下のように v でモジュールを取り除きます。

v remove [module]

例:

v remove ui

以下のように VPM からインストールしたモジュールを更新します。

v update [module]

例:

v update ui

または以下のようにすべてのモジュールを更新できます。

v update
``

インストールしたモジュールをすべて表示するには、以下を使用します。

```sh
v list
``

**例:**

```sh
> v list
Installed modules:
  markdown
  ui

インストールされているすべてのモジュールを見るには、以下のように outdated とします。これは更新が必要なインストール済みモジュールを表示します。

v outdated

例:

> v outdated
Modules are up to date.

パッケージの公開

  1. v.mod ファイルをモジュールのトップレベルフォルダ内に置いてください (v new mymodule または v init コマンドでモジュールを作成した場合は、すでに v.mod ファイルがあります)。

    v new mymodule
    Input your project description: My nice module.
    Input your project version: (0.0.0) 0.0.1
    Input your project license: (MIT)
    Initialising ...
    Complete!

    v.mod の例:

    Module {
        name: 'mymodule'
        description: 'My nice module.'
        version: '0.0.1'
        license: 'MIT'
        dependencies: []
    }
    

    ファイルの最小構成:

    v.mod
    mymodule.v
    

    そのモジュール名が mymodule.v で使用されていることを確認します。

    module mymodule
    
    pub fn hello_world() {
        println('Hello World!')
    }
    
  2. v.mod ファイルのあるフォルダに git リポジトリを作成します (v newv init を使用した場合は不要です)。

    git init
    git add .
    git commit -m "INIT"

  3. github.com 上に公開レポジトリを作成します。

  4. ローカルリポジトリとリモートリポジトリを接続し、変更をプッシュします。

  5. パブリック V モジュールレジストリ VPM: https://vpm.vlang.io/new にモジュールを追加します。

    モジュールを登録するには、GitHub アカウントでログインする必要があります。警告: 現在、投稿後にエントリーを編集できません。モジュール名と github の URL は後から変更できないので、ダブルチェックしてください

  6. 最終的なモジュール名は、github アカウントと、指定のモジュール名を組み合わせたものになります。

任意: V モジュールに vlangvlang-module のタグを付けると、github.com で検索しやすくなります。

型宣言

インターフェイス

struct Dog {
    breed string
}

struct Cat {
    breed string
}

fn (d Dog) speak() string {
    return 'woof'
}

fn (c Cat) speak() string {
    return 'meow'
}

// Go や TypeScript とは異なり、V のインターフェイスはメソッドだけでなくフィールドも定義できます。
interface Speaker {
    breed string
    speak() string
}

dog := Dog{'Leonberger'}
cat := Cat{'Siamese'}

mut arr := []Speaker{}
arr << dog
arr << cat
for item in arr {
    println('a $item.breed says: $item.speak()')
}

型がインターフェイスを実装するには、そのメソッドを実装することでできます。意図を明示的に宣言することはできず、"implements" キーワードもありません。

インターフェイスのキャスト

動的キャスト演算子を使用して、インターフェイスの基底型を判別できます。

fn announce(s Speaker) {
    if s is Dog {
        println('a $s.breed') // `s`は自動で `Dog` にキャストされます (スマートキャスト)
    } else if s is Cat {
        println('a cat')
    } else {
        println('something else')
    }
}

詳細は、動的キャスト を参照してください。

インターフェイスメソッドの定義

Go とは異なり、インターフェースではメソッドも実装できます。これらのメソッドは、そのインタフェースを実装する構造体に対して実装されるわけではありません。

構造体がインターフェイスと同名のメソッドを実装したうえでインタフェースに包まれている場合のみ、そのインタフェースに実装されているメソッドを呼び出せます。

struct Cat {}

fn (c Cat) speak() string {
    return 'meow!'
}

interface Adoptable {}

fn (a Adoptable) speak() string {
    return 'adopt me!'
}

fn new_adoptable() Adoptable {
    return Cat{}
}

fn main() {
    cat := Cat{}
    assert cat.speak() == 'meow!'
    a := new_adoptable()
    assert a.speak() == 'adopt me!'
    if a is Cat {
        println(a.speak()) // meow!
    }
}

関数型

型のエイリアスは、特定の関数シグネチャの名前に使用できます。以下はその例です。

type Filter = fn (string) string

これは、他の型と同様に機能します。例えば、関数が関数型の引数を取ることができます。

type Filter = fn (string) string

fn filter(s string, f Filter) string {
    return f(s)
}

V はダックタイピングを採用しているので、関数は関数型との互換性を宣言する必要はありません。

fn uppercase(s string) string {
    return s.to_upper()
}

// これで `uppercase` は Filter を必要とするあらゆる場所で使用できる

互換な関数は以下のように関数型へ明示的にキャストすることもできます。

my_filter := Filter(uppercase)

ここでのキャストは純粋に情報的なものです。繰り返しになりますが、ダックタイピングとは明示的にキャストしなくても結果的に型が同じになることを意味します。

my_filter := uppercase

代入した関数を引数として渡すこともできます。

println(filter('Hello world', my_filter)) // `HELLO WORLD` と出力する

もちろん、ローカル変数を使わずに直接渡すこともできます。

println(filter('Hello world', uppercase))

そして匿名関数でも同様に動作します。

println(filter('Hello world', fn (s string) string {
    return s.to_upper()
}))

完全な コード例はこちら でご覧ください。

列挙体

enum Color {
    red green blue
}

mut color := Color.red
// V は `color` が `Color` 型だとわかっています。ここで `color = Color.green` とする必要はありません。
color = .green
println(color) // "green"
match color {
    .red { println('the color was red') }
    .green { println('the color was green') }
    .blue { println('the color was blue') }
}

列挙型のマッチは、条件を全て網羅しているか、else ブランチを持たなければなりません。これにより、新しい列挙フィールドが追加された場合でも、コード内の全箇所で正しい処理が保証されます。

列挙体のフィールドでは、予約済みのキーワードを再利用できません。しかし、予約済みのキーワードは @ でエスケープできます。

enum Color {
    @none
    red
    green
    blue
}

color := Color.@none
println(color)

整数を列挙体のフィールドに代入することもできます。

enum Grocery {
    apple
    orange = 5
    pear
}

g1 := int(Grocery.apple)
g2 := int(Grocery.orange)
g3 := int(Grocery.pear)
println('Grocery IDs: $g1, $g2, $g3')

出力: Grocery IDs: 0, 5, 6.

列挙体の変数では演算できず、int に明示的にキャストしなければなりません。

直和型

直和型のインスタンスはいくつか異なる種類の値を保持できます。以下のように type キーワードで直和型を宣言できます。

struct Moon {}

struct Mars {}

struct Venus {}

type World = Moon | Mars | Venus

sum := World(Moon{})
assert sum.type_name() == 'Moon'
println(sum)

組み込みメソッド type_name は現在保持している型の名前を返します。

直和型を使用すると、再帰的な構造体を構築でき、その上で簡潔かつ強力なコードを書けます。

// V の二分木
struct Empty {}

struct Node {
    value f64
    left  Tree
    right Tree
}

type Tree = Empty | Node

// 全ノードの値を合計
fn sum(tree Tree) f64 {
    return match tree {
        Empty { f64(0) } // TODO: match を賢くして f64() を取り除く
        Node { tree.value + sum(tree.left) + sum(tree.right) }
    }
}

fn main() {
    left := Node{0.2, Empty{}, Empty{}}
    right := Node{0.3, Empty{}, Node{0.4, Empty{}, Empty{}}}
    tree := Node{0.5, left, right}
    println(sum(tree)) // 0.2 + 0.3 + 0.4 + 0.5 = 1.4
}

列挙体も構造体のようにメソッドを持つことができます。

enum Cycle {
    one
    two
    three
}

fn (c Cycle) next() Cycle {
    match c {
        .one {
            return .two
        }
        .two {
            return .three
        }
        .three {
            return .one
        }
    }
}

mut c := Cycle.one
for _ in 0 .. 10 {
    println(c)
    c = c.next()
}

出力:

one
two
three
one
two
three
one
two
three
one

動的キャスト

直和型の実際の型を確かめるには、以下のように sum is Type を使用します。特定の種類にキャストするには sum as Type とします。

struct Moon {}

struct Mars {}

struct Venus {}

type World = Mars | Moon | Venus

fn (m Mars) dust_storm() bool {
    return true
}

fn main() {
    mut w := World(Moon{})
    assert w is Moon

    w = Mars{}
    // `as` を使用して Mars インスタンスにアクセスします
    mars := w as Mars
    if mars.dust_storm() {
        println('bad weather!')
    }
}

as では wMars インスタンスを保持していないとパニックします。スマートキャストのほうが安全な方法です。

スマートキャスト

if w is Mars {
    assert typeof(w).name == 'Mars'
    if w.dust_storm() {
        println('bad weather!')
    }
}

wif文の本文の中にMars` という型を持っています。これは フロー感知型付け として知られています。以下のように変数名を指定することもできます。

if w is Mars as mars {
    assert typeof(w).name == 'World'
    if mars.dust_storm() {
        println('bad weather!')
    }
}

この場合の w は元の型のままです。この形は、w が単なる変数名ではなく複雑な式の場合に必要となるでしょう。

直和型のマッチング

match を使用して種類を特定することもできます。

struct Moon {}

struct Mars {}

struct Venus {}

type World = Mars | Moon | Venus

fn open_parachutes(n int) {
    println(n)
}

fn land(w World) {
    match w {
        Moon {} // 大気なし
        Mars {
            // 薄い大気
            open_parachutes(3)
        }
        Venus {
            // 濃い大気
            open_parachutes(1)
        }
    }
}

match は各列挙子に対してのパターンを用意するか、else 節が必要です。

struct Moon {}
struct Mars {}
struct Venus {}

type World = Moon | Mars | Venus

fn (m Moon) moon_walk() {}
fn (m Mars) shiver() {}
fn (v Venus) sweat() {}

fn pass_time(w World) {
    match w {
        // シャドーイングされたマッチ変数の使用例。この場合は `w` (スマートキャスト)
        Moon { w.moon_walk() }
        Mars { w.shiver() }
        else {}
    }
}

Option / Result 型とエラーハンドリング

Option 型は以下のように ?Type で宣言できます。

struct User {
    id   int
    name string
}

struct Repo {
    users []User
}

fn (r Repo) find_user_by_id(id int) ?User {
    for user in r.users {
        if user.id == id {
            // V は自動的にこれを Option 型にラップする  
            return user
        }
    }
    return error('User $id not found')
}

fn main() {
    repo := Repo{
        users: [User{1, 'Andrew'}, User{2, 'Bob'},
            User{10, 'Charles'},
        ]
    }
    user := repo.find_user_by_id(10) or { // Option 型は `or` ブロックでハンドリングする必要がある
        return
    }
    println(user.id) // "10"
    println(user.name) // "Charles"
}

V は OptionResult を一つの型に組み合わせているので、どちらを使うべきか決定する必要はありません。

関数を Optional な関数へ「アップグレード」する必要のある作業が山のようにあっても、その変更は小さくなります。戻り値の型に ? を加えて、何かがおかしいときにエラーを返すだけです。

エラーメッセージを返す必要が無い場合は、単に return none とします (これは return error("") よりかは効果的です)。

これは V でエラーを制御する主要な手段です。これらは値でもあり、Go のようですが、エラーを無視できないという利点があります。且つ、これらを制御することはそこまで煩わしくありません。

error ブロックの中で定義され、error() 関数で渡された文字列のメッセージがセットされています。none が返された場合は err は空です。

user := repo.find_user_by_id(7) or {
    println(err) // "User 7 not found"
    return
}

Optional のハンドリング

Optional のハンドリングには 4 つの方法があります。1 つ目はエラーを伝播させます。

import net.http

fn f(url string) ?string {
    resp := http.get(url) ?
    return resp.text
}

http.get?http.Response を返します。呼び出し ? を付けているため、このエラーは f の呼び出し元へと伝播されます。Option 型を返す関数呼び出しの後に ? を使用する場合、それを囲っている関数も同様にオプションを返さなければなりません。関数 main() でこのエラーの伝播を使用した場合、エラーはそれ以上伝播されずに、代わりに panic をします。

f の本文は、基本的には以下を短縮したものです。

    resp := http.get(url) or {
        return error(err)
    }
    return resp.text

2 つ目の手法は、実行を早めに切り上げることです。

user := repo.find_user_by_id(7) or { return }

ここでは、panic()exit() を呼び出してプログラム全体の実行を停止するか、制御フロー文 (returnbreakcontinue など) を使って現在のブロックから抜け出すことができます。注意として、breakcontinuefor ループ内でのみ使用できます。

V には強制的に Option を "unwrap" する方法 (他の言語では Rust の unwrap() や Swift の ! など) がありません。これをするには、代わりに or { panic(err) } とします。


3 つ目の手法は or ブロックの最後でデフォルト値を提供することです。この場合のエラー処理部は、その値が代わりに代入されます。そのため、Option の中身の型と同じ型を返す必要があります。

fn do_something(s string) ?string {
    if s == 'foo' { return 'foo' }
    return error('invalid string') // `return none` のようにもできる
}

a := do_something('foo') or { 'default' } // a will be 'foo'
b := do_something('bar') or { 'default' } // b will be 'default'

4 つ目の手法は if で取り出すものです。

if resp := http.get(url) {
    println(resp.text) // resp は http.Response であり、Option ではありません
} else {
    println(err)
}

上記では、http.get?http.Response を返します。resp のスコープは最初の if 節の中だけです。err のスコープは else 節の中だけです。

ジェネリクス

struct Repo<T> {
    db DB
}

fn new_repo<T>(db DB) Repo<T> {
    return Repo<T>{db: db}
}

// これはジェネリック関数です。V は使用される型ごとに関数を生成します。
fn (r Repo<T>) find_by_id(id int) ?T {
    table_name := T.name // この例では型の名前をテーブル名にしています
    return r.db.query_one<T>('select * from $table_name where id = ?', id)
}

db := new_db()
users_repo := new_repo<User>(db)
posts_repo := new_repo<Post>(db)
user := users_repo.find_by_id(1)?
post := posts_repo.find_by_id(1)?

現時点では、T という名前の型引数 1 つしかサポートされていません。

現在のところ、ジェネリック関数の定義では型引数を宣言しなければなりませ。しかし将来的には、実行時の引数型の中にある一文字の型名からジェネリック型引数を推測するようになります。このために find_by_id では <T> を省略できます。

他の例も示します。

fn compare<T>(a, b T) int {
    if a < b {
        return -1
    }
    if a > b {
        return 1
    }
    return 0
}

// compare<int>
println(compare<int>(1,0)) // 出力: 1
println(compare<int>(1,1)) //      0
println(compare<int>(1,2)) //     -1

// compare<string>
println(compare<string>('1','0')) // 出力: 1
println(compare<string>('1','1')) //      0
println(compare<string>('1','2')) //     -1

// compare<f64>
println(compare<float>(1.1, 1.0)) // 出力: 1
println(compare<float>(1.1, 1.1)) //      0
println(compare<float>(1.1, 1.2)) //     -1

並行計算

並行タスクの生成

V の並行処理モデルは Go のものと非常によく似ています。foo() を並列実行するには、go foo() と呼ぶだけです。

import math

fn p(a f64, b f64) { // 戻り値がない普通の関数
    c := math.sqrt(a * a + b * b)
    println(c)
}

fn main() {
    go p(3, 4)
    // p will be run in parallel thread
}

並列スレッドが終了するまで待たなければならないことがあるでしょう。これには、開始したスレッドの ハンドル を代入しておいて、後でこのハンドルに対して wait() メソッドを呼び出すことで可能です。

import math

fn p(a f64, b f64) { // 戻り値がない普通の関数
    c := math.sqrt(a * a + b * b)
    println(c) // `5` と出力する
}

fn main() {
    h := go p(3, 4)
    // p() は並列スレッドは実行する
    h.wait()
    // p() は確実に終了する
}

このアプローチは、並列スレッドが実行している関数から戻り値を取得する場合にも利用できます。並行呼び出しできるように関数自体を修正する必要はありません。

import math { sqrt }

fn get_hypot(a f64, b f64) f64 { // 戻り値がある普通の関数
    c := sqrt(a * a + b * b)
    return c
}

fn main() {
    g := go get_hypot(54.06, 2.08) // スレッドを生成してそのハンドルを得る
    h1 := get_hypot(2.32, 16.74) //   ここで他の計算もする
    h2 := g.wait() //                 生成したスレッドから結果を取得する
    println('Results: $h1, $h2') //   `Results: 16.9, 54.1` と出力する
}

値を返さないタスクが多数ある場合は、待ちグループを使って管理する方が簡単かもしれません。しかしこのアプローチでは、待ちグループが関数を並行して呼び出すことを念頭に置いて関数を設計しなければなりません。

import time

fn task(id int, duration int) {
    println('task $id begin')
    time.sleep(duration * time.millisecond)
    println('task $id end')
}

fn main() {
    mut threads := []thread{}
    threads << go task(1, 500)
    threads << go task(2, 900)
    threads << go task(3, 100)
    threads.wait()
    println('done')
}

// 出力:
// task 1 begin
// task 2 begin
// task 3 begin
// task 3 end
// task 1 end
// task 2 end
// done

さらに、同じ型を返すスレッドの配列に対して wait() を呼び出すと、計算されたすべての値が返されます。

fn expensive_computing(i int) int {
    return i * i
}

fn main() {
    mut threads := []thread int{}
    for i in 1 .. 10 {
        threads << go expensive_computing(i)
    }
    // Join all tasks
    r := threads.wait()
    println('All jobs finished: $r')
}

// 出力: All jobs finished: [1, 4, 9, 16, 25, 36, 49, 64, 81]

チャンネル

チャンネルは、コアーチン間の通信に適した方法です。V のチャンネルは基本的に Go のチャンネルと同じように動作します。一方からオブジェクトをチャンネルに押し込み、他方からオブジェクトを取り出すことができます。チャンネルはバッファしたりしなかったり、複数のチャンネルから select したりできます。

構文と使用方法

チャンネルの型は chan objtype です。宣言では、オプションでバッファの長さを cap プロパティとして指定することができます。

ch := chan int{} // バッファされない - "同期的"
ch2 := chan f64{cap: 100} // バッファ長 100

チャンネルは mut として宣言する必要はありません。バッファの長さは型の一部ではなく、個々のチャンネルオブジェクトのプロパティです。チャンネルは、通常の変数のようにコアーチンに渡すことができます。

fn f(ch chan int) {
    // ...
}

fn main() {
    ch := chan int{}
    go f(ch)
    // ...
}

オブジェクトはアロー演算子を使ってチャンネルに押し込むことができます。同じ演算子で他方からオブジェクトを取り出すことができます。

// バッファリングされたチャンネルを作り、(バッファに余裕がある場合) push がブロックされないようにする
ch := chan int{cap: 1}
ch2 := chan f64{cap: 1}
n := 5
// push
ch <- n
ch2 <- 7.3
mut y := f64(0.0)
m := <-ch // 新しい変数を作って取り出す
y = <-ch2 // 既存の変数に取り出す

チャンネルを閉じると、それ以上オブジェクトを押し込めなくなります。それでも押し込もうとすると、実行時にパニックが発生します (selecttry_push() は例外です - 以下を参照)。関連するチャンネルが閉じられバッファが空になっている場合は、取り出そうとすると即座に return します。このような状況を処理するには、or 節を使用します (Optional のハンドリング を参照)。

ch := chan int{}
ch2 := chan f64{}
// ...
ch.close()
// ...
m := <-ch or {
    println('channel has been closed')
}

// エラーの伝播
y := <-ch2 ?

チャンネルの select

select コマンドを使うと、CPU に顕著な負荷をかけることなく複数のチャンネルを同時に監視することができます。これは match 命令のように、転送可能なステートメントのリストと関連する節で構成されています。

import time

fn main() {
    mut c := chan f64{}
    mut ch := chan f64{}
    mut ch2 := chan f64{}
    mut ch3 := chan f64{}
    mut b := 0.0
    c := 1.0
    // ... ch/ch2 に送信する go スレッドのセットアップ
    go fn (the_channel chan f64) {
        time.sleep(5 * time.millisecond)
        the_channel <- 1.0
    }(ch)
    go fn (the_channel chan f64) {
        time.sleep(1 * time.millisecond)
        the_channel <- 1.0
    }(ch2)
    go fn (the_channel chan f64) {
        _ := <-the_channel
    }(ch3)
    //
    select {
        a := <-ch {
            // `a` でなにかする
            eprintln('> a: $a')
        }
        b = <-ch2 {
            // 事前に宣言した `b` でなにかする
            eprintln('> b: $b')
        }
        ch3 <- c {
            // `c` が送られた場合になにかする
            time.sleep(5 * time.millisecond)
            eprintln('> c: $c was send on channel ch3')
        }
        500 * time.millisecond {
            // どのチャンネルも準備状態にならずに 0.5 秒経過したらなにかする
            eprintln('> more than 0.5s passed without a channel being ready')
        }
    }
}

タイムアウト節は任意です。省略した場合、select は無制限に待機します。また、else { ... } 節を追加することで、select が呼ばれた瞬間にチャネルの準備ができていない場合でもすぐに処理を進められます。else> timeout を同時に両方定義することはできません。

select コマンドは、すべてのチャンネルが閉じている場合に false になる bool 型の式として利用できます。

if select {
    ch <- a {
        ...
    }
} {
    // チャンネルが開いていた
} else {
    // チャンネルが閉じられた
}

特殊なチャンネルの機能

特別な目的のために、いくつかの組み込みプロパティとメソッドがあります。

struct Abc {
    x int
}

a := 2.13
ch := chan f64{}
res := ch.try_push(a) // `ch <- a` を試みる
println(res)
l := ch.len // キューの最大要素数
c := ch.cap // キューの最大長
is_closed := ch.closed // bool のフラグ - `ch` が閉じているかどうか
println(l)
println(c)
mut b := Abc{}
ch2 := chan Abc{}
res2 := ch2.try_pop(b) // `b = <-ch2` を試みる

try_push/pop() メソッドは即座に .succeed.not_ready.closed のいずれかの結果を返しますが、これはオブジェクトが転送されたかどうかや転送されなかった理由に依存します。これらのメソッドやプロパティを本番環境で使用することは推奨されません。これらのメソッドやプロパティに基づくアルゴリズムはしばしば競合状態に陥ります。代わりに select を使用してください (上記の 構文と使用方法 及び チャンネルの select を参照)。

共有オブジェクト

データは、コルーチンと呼び出し元のスレッドの間で shared 変数を介して交換できます。このような変数は共有変数として作成してからコルーチンに渡す必要があります。基底となる struct は暗黙の mutex を保持します。これは rlock を用いて読み込み専用のアクセス、lock を用いて読み込み/書き込みのアクセスをロックできます。

struct St {
mut:
    x int // 共有するデータ
}

fn (shared b St) g() {
    lock b {
        // b.x の読み込み/変更/書き込み
    }
}

fn main() {
    shared a := St{
        x: 10
    }
    go a.g()
    // ...
    rlock a {
        // a.x の読み込み
    }
}

共有変数は構造体、配列、辞書配列のいずれかでなければなりません。

JSON のデコード

import json

struct Foo {
    x int
}

struct User {
    name string
    age  int
    // `skip` 属性を使用することで指定のフィールドをスキップできます
    foo Foo [skip]
    // フィールド名が JSON と異なる場合は、名前を指定できます 
    last_name string [json:lastName]
}

data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25 }'
user := json.decode(User, data) or {
    eprintln('Failed to decode json')
    return
}
println(user.name)
println(user.last_name)
println(user.age)
// JSON 配列もデコードできます
sfoos := '[{"x":123},{"x":456}]'
foos := json.decode([]Foo, sfoos)?
println(foos[0].x)
println(foos[1].x)

JSON は今日ではとても広く使われているため、V では直接 JSON サポートを組み込んであります。

json.decode 関数は引数を 2 つ取ります。json.decode 関数の第 1 引数はデコード後に変換される型で、第 2 引数は JSON データの入った文字列です。

V は JSON エンコードやデコードするコードを生成します。実行時リフレクションは使われていません。そのため、それなりのパフォーマンスを発揮します。

テスト

アサーション

mut v := 2 
foo(mut v)
assert v < 4 

assert 文は式の評価結果が true であることを確認します。アサーションに失敗すると、プログラムは中断します。アサーションはプログラムの整合性を検知するためだけに使用するべきです。アサーションに失敗すると 標準エラー に報告され、比較演算子 (<==) の両側の値を可能であれば出力します。これにより予期せぬ値を簡単に見つけることができます。assert 文はあらゆる関数内で使用できます。

テストファイル

hello.v
module main
fn hello() string {
    return 'Hello world' 
}
fn main() {
    println(hello())
}
hello_test.v
module main
fn test_hello() {
    assert hello() == 'Hello world'
} 

上記のテストを実行するには、v hello_test.v を使います。これは関数 hello が正しい出力を生成しているかどうかを確認するものです。V はこのファイル内のすべてのテスト関数を実行します。

  • すべてのテスト関数は、名前が _test.v で終わるテストファイル内に存在する必要があります。
  • テスト関数名は、実行する印として test_ で始まる必要があります。
  • 通常の関数もテストファイルで定義できますが、これは手動で呼び出す必要があります。その他のシンボルもテストファイルで定義できます。
  • テストには外部と内部の 2 種類があります。
  • 内部テストは、同モジュールの他すべての .v ファイルのようにモジュールを 宣言 しなければなりません。内部テストでは、同じモジュール内のプライベート関数を呼び出すことができます。
  • 外部テストは、テストするモジュールを インポート しなければなりません。外部テストはモジュールのプライベートな関数/型にはアクセスできません。モジュールが提供する外部/公開 API のみをテストすることができます。

上の例では、test_hello は内部テストで、プライベート関数 hello() を呼び出すことができます。また、注意として、module main は他のモジュールと同様に通常のモジュールなので、内部テストはメインプログラムの .v ファイル内のプライベート関数もテストできます。

また、テストファイルに以下の特殊なテスト関数も定義できます。

  • testsuite_begin は、他のすべてのテスト関数の前に実行されます。
  • testsuite_end は、他のすべてのテスト関数の後に実行されます。

テストの実行

個々のテストファイルに対してテスト関数を実行するには、v foo_test.v を使います。

モジュール全体をテストするには、v test mymodule とします。また、現在のフォルダ (およびサブフォルダ) 内のすべてをテストするには v test . でもできます。オプション -stats を渡すと、実行された個々のテストの詳細を見られます。

.v のソースファイルを含む追加のテストデータを、_test.v ファイルと同階層の testdata フォルダに置くことができます。V のテストフレームワークは、そのようなフォルダを無視して実行するテストをスキャンします。これは、無効な V ソースコードを含む .v ファイルや、既知の失敗テストを含む上位階層の _test.v ファイルによって特定の方法/オプションで実行されるべきテストを置きたい場合に便利な機能です。

注: V コンパイラへのパスは @VEXE で取得できるので、_test.v ファイルでは以下ように他のテストファイルを簡単に実行できます。

import os

fn test_subtest() {
    res := os.execute('${@VEXE} other_test.v')
    assert res.exit_code == 1
    assert res.output.contains('other_test.v does not exist')
}

メモリ管理

V は、値型や文字列バッファを使用することで、そもそもの不要なアロケーションを避け、シンプルで抽象のないコードスタイルを推奨しています。

ほとんどのオブジェクト (~90-100%) は V の自動解放エンジンによって解放されます。残りのわずかな割合のオブジェクトは、参照カウント方式で解放されます。

開発者のコード変更は不要です。Python、Go、Java と違って、全体をトレースする重い GC やオブジェクトごとの高コストな参照カウント無しに「よく動きます」。

制御

V の autofree エンジンを利用して、カスタムデータ型に free() メソッドを定義できます。

struct MyType {}

[unsafe]
fn (data &MyType) free() {
    // ...
}

コンパイラが C のデータ型を C の free() で解放するのと同じように、データ型ごとの free() 呼び出しを各変数の寿命の終わりに静的に挿入します。

よりローレベルでの制御を望む開発者のために、-manualfree で自動解放を無効にしたり、手動メモリを管理したい各関数に[manualfree] を追加したりできます (属性 を参照)。

注意: 現在、autofree は -autofree フラグを付けない限り無効です。V 0.3 ではデフォルトで有効になります。autofree が使用されていない場合、Vプログラムはメモリをリークします

import strings

fn draw_text(s string, x int, y int) {
    // ...
}

fn draw_scene() {
    // ...
    name1 := 'abc'
    name2 := 'def ghi'
    draw_text('hello $name1', 10, 10)
    draw_text('hello $name2', 100, 10)
    draw_text(strings.repeat(`X`, 10000), 10, 50)
    // ...
}

文字列は draw_text より外に出ていないので、これらは関数を抜けるときに解放されます。

実際に最初の 2 つの呼び出しはすべて、結果的にメモリ確保をしていません。これら 2 つの文字列は小さいので、V はこれらのために事前確保したバッファを使用します。

struct User {
    name string
}

fn test() []int {
    number := 7 // スタック変数
    user := User{} // スタック上に確保された構造体
    numbers := [1, 2, 3] // ヒープ上に確保された配列、関数を抜けるときに解放される
    println(number)
    println(user)
    println(numbers)
    numbers2 := [4, 5, 6] // 配列は返されるので、ここでは解放されない
    return numbers2
}

スタックとヒープ

スタックとヒープの基礎

他の多くのプログラミング言語と同様に、データを保存できる場所が 2 つあります。

  • スタック は、管理上のオーバーヘッドをほぼゼロにして、高速な割り当てを可能にします。スタックは関数の呼び出し回数に応じて大きくなったり小さくなったりします。したがって、呼び出された関数はすべて、その関数が戻るまで有効なスタックセグメントを持ちます。解放する必要はありませんが、このことは、スタックオブジェクトへの参照が関数の戻り時に無効になることを意味します。さらに、スタックスペースには制限があります (通常、1 スレッドあたり数メガバイト)。
  • ヒープ は、オペレーティングシステムによって管理される大規模なメモリ領域 (通常は数ギガバイト) です。ヒープオブジェクトは、管理タスクを OS に委ねる特別な関数コールによって割り当てられ、解放されます。そのため、数回の関数呼び出しを経ても有効な状態を保つことができますが、管理にはコストがかかります。

V の既定の挙動

パフォーマンスを考慮して、V は可能な限りオブジェクトをスタックに置くようにしていますが、明確に必要な場合はヒープに割り当てます。以下はその例です。

struct MyStruct {
    n int
}

struct RefStruct {
    r &MyStruct
}

fn main() {
    q, w := f()
    println('q: $q.r.n, w: $w.n')
}

fn f() (RefStruct, &MyStruct) {
    a := MyStruct{
        n: 1
    }
    b := MyStruct{
        n: 2
    }
    c := MyStruct{
        n: 3
    }
    e := RefStruct{
        r: &b
    }
    x := a.n + c.n
    println('x: $x')
    return e, &c
}

ここで、a はそのアドレスが関数 f() から離れることがないので、スタックに格納されます。しかし、b への参照は返される e の一部です。また、cへの参照も返されます。これにより、bc はヒープに割り当てられます。

オブジェクトへの参照が関数の引数として渡される場合には、事態はより明白になります.

struct MyStruct {
mut:
    n int
}

fn main() {
    mut q := MyStruct{
        n: 7
    }
    w := MyStruct{
        n: 13
    }
    x := q.f(&w) // `q` と `w` の参照が渡される
    println('q: $q\nx: $x')
}

fn (mut a MyStruct) f(b &MyStruct) int {
    a.n += b.n
    x := a.n * b.n
    return x
}

ここでは、 q.f(&w) という呼び出しが qw への参照を渡しています。これは f() の宣言で amutb&MyStruct 型であるためで、技術的にはこれらの参照は main() から離れることになります。しかし、これらの参照の ライフタイムmain() のスコープ内にあるので、 qw はスタック上に割り当てられます。

スタックとヒープの手動制御

先程の例で V コンパイラが qw をスタックに置くことができたのは、q.f(&w) の呼び出しにおいて、これら参照が値を読み込んだり変更したりするためにのみ使用され、参照自体をどこか他の場所に渡すためには使用されないと仮定したからです。これは、qw への参照が f() にのみ借用されるという形で見ることができます。

f() が参照そのものを使って何かをしている場合には、状況が変わってきます。

struct RefStruct {
mut:
    r &MyStruct
}

// 下記の議論を参照
[heap]
struct MyStruct {
    n int
}

fn main() {
    m := MyStruct{}
    mut r := RefStruct{
        r: &m
    }
    r.g()
    println('r: $r')
}

fn (mut r RefStruct) g() {
    s := MyStruct{
        n: 7
    }
    r.f(&s) // `r` 内で `s` への参照を `main()` へ戻して渡す 
}

fn (mut r RefStruct) f(s &MyStruct) {
    r.r = s // `[heap]` なしだとエラーを起こす
}

ここでは f() は何の変哲もないように見えますが、厄介なことをしています。この問題は、sg() が実行されている間だけ生きていますが、r はその後 main() で使用されるということです。このため、コンパイラは f() での代入について、s「スタックに格納されているオブジェクトを参照している可能性がある」 という理由で文句を言います。g() における r.f(&s) の呼び出しは s への参照を借用するだけである、という仮定が間違っています。

このジレンマを解決する方法として、struct MyStruct の宣言に [heap] 属性を設定する方法があります。これはコンパイラに、MyStruct オブジェクトを常にヒープ上に確保するように指示します。こうすることで、g() が戻ってきた後も s への参照が有効になります。コンパイラは f() をチェックする際に MyStruct オブジェクトが常にヒープ上に確保されていることを考慮して、s への参照を r.r フィールドへ代入することを許可します。

以下のような他のプログラミング言語でよく見られるパターンがあります。

fn (mut a MyStruct) f() &MyStruct {
    // a でなにかする
    return &a // 借用したオブジェクトのアドレスを返す
}

ここでは、f() はレシーバとして参照 a を渡され、それが呼び出し側に返されると同時に結果として戻ります。このような宣言は、y = x.f().g() のようなメソッドチェーンを意図しています。しかし、この方法には a に対する 2 つ目の参照 a が作成されるという問題点があります。つまり、単なる借用にできないため MyStruct[heap] として宣言しなければなりません。

V では、以下のようなより良いアプローチを取ります。

struct MyStruct {
mut:
    n int
}

fn (mut a MyStruct) f() {
    // `a` でなにかをする
}

fn (mut a MyStruct) g() {
    // `a` で他のなにかをする
}

fn main() {
    x := MyStruct{} // スタックに確保
    mut y := x
    y.f()
    y.g()
    // `mut y := x.f().g() の代わり
}

このように [heap] 属性を回避でき、結果的にパフォーマンスが向上します。

しかし、上述のようにスタックスペースは非常に限られています。よって、上述のようなユースケースでは必要ないとしても、非常に大きな構造体には [heap] 属性が適しているかもしれません。

ケースバイケースで割り当てを手動制御する方法もあります。この方法は推奨されていませんが、念のためにここで紹介します。

struct MyStruct {
    n int
}

struct RefStruct {
mut:
    r &MyStruct
}

// シンプルな関数 - g() が使うスタックセグメントの上書き
fn use_stack() {
    x := 7.5
    y := 3.25
    z := x + y
    println('$x $y $z')
}

fn main() {
    m := MyStruct{}
    mut r := RefStruct{
        r: &m
    }
    r.g()
    use_stack() // 無効なスタックの中身を消去する
    println('r: $r')
}

fn (mut r RefStruct) g() {
    s := &MyStruct{ // `s` がヒープのオブジェクトを参照すると明示
        n: 7
    }
    // 上書きされるスタックセグメント内のデータを見には
    // 上を `&MyStruct` -> `MyStruct`、下を `r.f(s)` -> `r.f(&s)` に変更
    r.f(s)
}

fn (mut r RefStruct) f(s &MyStruct) {
    r.r = unsafe { s } // コンパイラのチェックを上書き
}

ここでは、コンパイラのチェックは unsafe ブロックによって抑制されています。[heap] 属性がなくても s をヒープに割り当てるようにするために、構造体リテラルの前にアンパサンドを付けています。

この最後のステップはコンパイラでは必要ありませんが、これがないと r 内部の参照が無効になり (指されたメモリ領域が use_stack() で上書きされてしまう)、プログラムがクラッシュする (あるいは少なくとも予測できない最終出力を生成する) 可能性があります。これが、この方法が安全でなく、避けるべき理由です。

ORM

(これはまだアルファ段階です)

V は組み込みの ORM サポートがあります。Postgres をサポートしており、まもなく MySQL と SQLite もサポートされます。

このアプローチには以下のようなメリットがあります。

  • すべての SQL 方言を一つの構文で。(異なるデータベースの統合がより簡単になります。)
  • クエリは V の構文で構築します。(他の構文を学ぶ必要はありません。)
  • 安全性。(もう SQL クエリの構築でインジェクションはありえません。)
  • コンパイル時チェック。(実行時にしか現れない打ち間違いがなくなります。)
  • 読みやすくシンプルに。(もう結果を手動でパースしたりオブジェクトを構築する必要はありません。)
import sqlite

struct Customer {
    // 現在、構造体の名前はテーブル名と同じである必要があります
    id int // 整数の id が最初のフィールドでなければなりません
    name string
    nr_orders int
    country string
}

db := sqlite.connect('customers.db')?
// select count(*) from Customer
nr_customers := sql db { select count from Customer }
println('number of all customers: $nr_customers')
// V 構文はクエリの構築に使用できます
// db.select は配列を返します
uk_customers = sql db { select from Customer where country == 'uk' && nr_orders > 0 }
println(uk_customers.len)
for customer in uk_customers {
    println('$customer.id - $customer.name')
}
// `limit 1` を加えることで V に一つのオブジェクトしか要らないことを伝えます
customer = sql db { select from Customer where id == 1 limit 1 }
println('$customer.id - $customer.name')
// 新しい顧客情報を挿入します
new_customer := Customer{name: 'Bob', nr_orders: 10}
sql db {
  insert new_customer into Customer
}

他のサンプルについては、vlib/orm/orm_test.v を参照してください。

ドキュメントを書く

やり方は Go と非常に似通っており、とてもシンプルです。コードにドキュメントを書く必要がなければ、vdoc がソースコードから生成します。関数/型/定数のドキュメントはこのように宣言の前に正しく配置されなければなりません。

// clearall は配列の全ビットを消去する
fn clearall() {
}

そのコメントは定義される名前から始まる必要があります。

モジュールの概要は、モジュール名の直後の最初のコメントに配置する必要があります。

ドキュメントを生成するには vdoc を、例えば v doc net.http のように実行します。

ツール

vfmt

もうコードフォーマットやスタイルガイドラインを気にする必要はありません。vfmt はこれだけでうまくやってくれます。

v fmt file.v

vfmt が保存するたびに実行されるように、エディタをセットアップすることを推奨します。vfmt の実行はとても速い (30ms 未満) です。

コードをプッシュする前に、常に v fmt -w file.v を実行しましょう。

プロファイリング

v -profile profile.txt run file.v を実行すると、profile.txt ファイルが生成され、これを分析できます。

生成された Pprofile.txtファイル`には、次の 4 つの列を持つ行が含まれています。

  1. 関数が呼び出された回数
  2. 関数での総時間 (単位:ms)
  3. 関数呼び出しにかかった平均時間 (単位:ns)
  4. v の関数名

sort -n -k3 profile.txt|tail で、3 列目 (関数ごとの平均時間) をソートできます。

以下のように、ストップウォッチを使用してコードの一部だけを明示的に測定することもできます。

import time

fn main() {
    sw := time.new_stopwatch({})
    println('Hello world')
    println('Greeting the world took: ${sw.elapsed().nanoseconds()}ns')
}

発展的なトピック

実行時に式を出力

dump(expr) で、任意の V 式の値をダンプ/トレースできます。例えば、以下のコードサンプルを factorial.v として保存し、v run factorial.v で実行してみてください。

fn factorial(n u32) u32 {
    if dump(n <= 1) {
        return dump(1)
    }
    return dump(n * factorial(n - 1))
}

fn main() {
    println(factorial(5))
}

以下のような出力が得られます。

[factorial.v:2] n <= 1: false
[factorial.v:2] n <= 1: false
[factorial.v:2] n <= 1: false
[factorial.v:2] n <= 1: false
[factorial.v:2] n <= 1: true
[factorial.v:3] 1: 1
[factorial.v:5] n * factorial(n - 1): 2
[factorial.v:5] n * factorial(n - 1): 6
[factorial.v:5] n * factorial(n - 1): 24
[factorial.v:5] n * factorial(n - 1): 120
120

なお、dump(expr) は、ソースの位置、式自体、式の値の両方をトレースします。

メモリ安全でないコード

効率化のために、メモリを破損させたりセキュリティ攻撃を受けたりしかねない低水準のコードを書きたくなることがあるでしょう。V はこのようなコードの記述をサポートしていますが、デフォルトではサポートされていません。

V では、メモリ安全性に問題がある可能性のある操作には意図的にマーキングすることを要求しています。また、これらの操作にマーキングすることで、コードを読んでいる人に、もし間違いがあった場合はメモリ安全性に違反しうると示すことができます。

メモリが安全でない可能性のある操作の例としては、以下のようなものがあります。

  • ポインタ演算
  • ポインタの配列扱い
  • 非互換型からポインタへの変換
  • freestrlenstrncmp などの C 関数の呼び出し

メモリが安全でない可能性のある操作をマークするには、unsafe ブロックで囲みます。

// 未初期化バイトを 2 つ確保 & その参照を返す
mut p := unsafe { malloc(2) }
p[0] = `h` // エラー: ポインタの配列扱いは `unsafe` ブロック内でのみ可能
unsafe {
    p[0] = `h` // OK
    p[1] = `i`
}
p++ // エラー: ポインタ演算は `unsafe` ブロック内でのみ可能
unsafe {
    p++ // OK
}
assert *p == `i`

ベストプラクティスとしては、unsafe ブロックの中にメモリセーフな式を入れないようにし、unsafe ブロックを使用する理由をできるだけ明確にすることです。一般的に、メモリ安全と思われるコードは、コンパイラがそれを検証できるように unsafe ブロック内に入れないようにしましょう。

プログラムがメモリ安全性に違反していると思われる場合は、その原因を見つけるための足がかりとして unsafe ブロック (とそれに接する周り) を見ればよいのです。

  • 注: これは作業中です。

参照のフィールドを持つ構造体

参照を持つ構造体は、構造体がすでに独自の初期値を定義していない限り、初期値を明示的な参照値に設定する必要があります。

ゼロ値参照や nil ポインタは将来的にサポート しません が、今のところ参照フィールドは値 0 を使用できます。これに依存するリンクリストや二分木のようなデータ構造は、それが安全ではないことを理解し、パニックを引き起こす可能性があることを理解してください。

struct Node {
    a &Node
    b &Node = 0 // 自動で nil に初期化されます、使用の際はご注意ください!
}

// 初期値が宣言されていない場合、参照フィールドは初期化が必要です。
// ゼロ (0) は OK ですが気をつけてください。これは nil ポインタです。
foo := Node{
    a: 0
}
bar := Node{
    a: &foo
}
baz := Node{
    a: 0
    b: 0
}
qux := Node{
    a: &foo
    b: &bar
}
println(baz)
println(qux)

sizeof と __offsetof

  • sizeof(型) は型のサイズをバイト単位で与えます。
  • __offsetof(構造体, フィールド名) は構造体のフィールドのオフセットをバイト単位で与えます。
struct Foo {
    a int
    b int
}

assert sizeof(Foo) == 8
assert __offsetof(Foo, a) == 0
assert __offsetof(Foo, b) == 4

V から C 関数を呼び出す

#flag -lsqlite3
#include "sqlite3.h"
// さらなるサンプルは https://www.sqlite.org/quickstart.html を参照
struct C.sqlite3 {
}

struct C.sqlite3_stmt {
}

type FnSqlite3Callback = fn (voidptr, int, &charptr, &charptr) int

fn C.sqlite3_open(charptr, &&C.sqlite3) int

fn C.sqlite3_close(&C.sqlite3) int

fn C.sqlite3_column_int(stmt &C.sqlite3_stmt, n int) int

// ... さらに引数型を定義して C. 接頭詞の省略もできる
fn C.sqlite3_prepare_v2(&sqlite3, charptr, int, &&sqlite3_stmt, &charptr) int

fn C.sqlite3_step(&sqlite3_stmt)

fn C.sqlite3_finalize(&sqlite3_stmt)

fn C.sqlite3_exec(db &sqlite3, sql charptr, FnSqlite3Callback voidptr, cb_arg voidptr, emsg &charptr) int

fn C.sqlite3_free(voidptr)

fn my_callback(arg voidptr, howmany int, cvalues &charptr, cnames &charptr) int {
    unsafe {
        for i in 0 .. howmany {
            print('| ${cstring_to_vstring(cnames[i])}: ${cstring_to_vstring(cvalues[i]):20} ')
        }
    }
    println('|')
    return 0
}

fn main() {
    db := &C.sqlite3(0) // `sqlite3* db = 0` と同義
    // C 関数の呼び出しに文字列リテラルを渡すと、V 文字列ではなく C 文字列になります。
    C.sqlite3_open('users.db', &db)
    // C.sqlite3_open(db_path.str, &db)
    query := 'select count(*) from users'
    stmt := &C.sqlite3_stmt(0)
    // 注意: V 文字列の `.str` フィールドを使用して、
    // C 方式のゼロ終端表現の取得もできます。
    C.sqlite3_prepare_v2(db, query.str, -1, &stmt, 0)
    C.sqlite3_step(stmt)
    nr_users := C.sqlite3_column_int(stmt, 0)
    C.sqlite3_finalize(stmt)
    println('There are $nr_users users in the database.')
    //
    error_msg := charptr(0)
    query_all_users := 'select * from users'
    rc := C.sqlite3_exec(db, query_all_users.str, my_callback, 7, &error_msg)
    if rc != C.SQLITE_OK {
        eprintln(cstring_to_vstring(error_msg))
        C.sqlite3_free(error_msg)
    }
    C.sqlite3_close(db)
} 

C から V を呼び出す

V は C にコンパイルできるので、C から V のコードを呼び出すのはとても簡単です。

デフォルトでは、すべての V の関数は、C では次のような命名法になっています: [モジュール名]__[fn名].

例えば、モジュール barfn foo() {} とすると、bar__foo() となります。

カスタムのエクスポート名を使用するには,[export] 属性を使用します。

[export: 'my_custom_c_name']
fn foo() {
}

アトミック

atomic_store() のような C11 の標準的なアトミック関数は、通常、マクロや C コンパイラのマジックの助けを借りて定義され、オーバーロードされた C 関数のようなものを提供しています。V は意図的に関数のオーバーロードをサポートしていないので、V コンパイラのインフラストラクチャの一部である atomic.h という名前の C ヘッダで定義されたラッパー関数があります。

すべての符号なし整数型とポインターに専用のラッパーがあります。例えば、C.atomic_load_ptr()C.atomic_fetch_add_u64() のように、関数名には型名がサフィックスとして含まれています。

これらの関数を使用するには,使用 OS の C ヘッダをインクルードし,使用する関数を宣言する必要があります。以下は例です。

$if windows {
    #include "@VEXEROOT/thirdparty/stdatomic/win/atomic.h"
} $else {
    #include "@VEXEROOT/thirdparty/stdatomic/nix/atomic.h"
}

// 使いたい関数の宣言 - V は C ヘッダを解析しません
fn C.atomic_store_u32(&u32, u32)
fn C.atomic_load_u32(&u32) u32
fn C.atomic_compare_exchange_weak_u32(&u32, &u32, u32) bool
fn C.atomic_compare_exchange_strong_u32(&u32, &u32, u32) bool

const num_iterations = 10000000

// 下記の「グローバル変数」の章を参照
__global (
    atom u32 // アトミックとして使用する普通の変数
)

fn change() int {
    mut races_won_by_change := 0
    for {
        mut cmp := u32(17) // 比較対象となるアドレス値と、発見された値を格納する
        // `if atom == 17 { atom = 23 races_won_by_change++ } else { cmp = atom }` のアトミック版
        if C.atomic_compare_exchange_strong_u32(&atom, &cmp, 23) {
            races_won_by_change++
        } else {
            if cmp == 31 {
                break
            }
            cmp = 17 // atom の値が上書きされたので再代入する
        }
    }
    return races_won_by_change
}

fn main() {
    C.atomic_store_u32(&atom, 17)
    t := go change()
    mut races_won_by_main := 0
    mut cmp17 := u32(17)
    mut cmp23 := u32(23)
    for i in 0 .. num_iterations {
        //`if atom == 17 { atom = 23 races_won_by_main++ }` のアトミック版
        if C.atomic_compare_exchange_strong_u32(&atom, &cmp17, 23) {
            races_won_by_main++
        } else {
            cmp17 = 17
        }
        desir := if i == num_iterations - 1 { u32(31) } else { u32(17) }
        // `for atom != 23 {} atom = desir` のアトミック版
        for !C.atomic_compare_exchange_weak_u32(&atom, &cmp23, desir) {
            cmp23 = 23
        }
    }
    races_won_by_change := t.wait()
    atom_new := C.atomic_load_u32(&atom)
    println('atom: $atom_new, #exchanges: ${races_won_by_main + races_won_by_change}')
    // `atom: 31, #exchanges: 10000000`) と出力する
    println('races won by\n- `main()`: $races_won_by_main\n- `change()`: $races_won_by_change')
}

この例では、main() と生成されたスレッド change() の両方が、グローバル変数 atom17 の値を 23 の値に置き換えようとします。逆方向への置換はちょうど 10000000 回行われます。最後の置換は 31 で行われ、生成されたスレッドは終了します。

どのスレッドで何回置換が行われるかは予測できませんが、合計は常に 10000000 になります (コメントにある非アトミック版では、この値はもっと大きくなるか、プログラムがハングアップします。これは使用するコンパイラの最適化に依存します)。

グローバル変数

V はデフォルトではグローバル変数を許可していません。しかし、ローレベルのアプリケーションではグローバル変数を使用する必要があるため、コンパイラフラグ -enable-globals を使用してその使用を有効化できます。グローバル変数の宣言は、上記 の例のように、__global ( ... ) で囲まなければなりません。

グローバル変数の初期化子は,明示的に目的の型に変換しなければなりません。初期化子が指定されていない場合は,デフォルトの初期化が行われます。セマフォやミューテックスのようないくつかのオブジェクトは、明示的な初期化を必要とします。つまり、関数呼び出しから返される値ではなく、参照によるメソッド呼び出しで初期化されます。この目的のために、別の init() 関数を使用することができます。

import sync

__global (
    sem   sync.Semaphore // `init()` 内で初期化が必要
    mtx   sync.RwMutex // `init()` 内で初期化が必要
    f1    = f64(34.0625) // 明示的に初期化
    shmap shared map[string]f64 // 空の `shared` 辞書配列として初期化
    f2    f64 // `0.0` に初期化
)

fn init() {
    sem.init(0)
    mtx.init()
}

マルチスレッドのアプリケーションでは、グローバル変数へのアクセスにレースコンディションが発生することに注意してください。この問題に対処するにはいくつかの方法があります。

  • 変数の宣言には shared を使用し、アクセスには lock ブロックを使用します。この方法は、構造体や配列、マップなどの大きなオブジェクトに最適です。
  • 特別な C 関数 (上記 参照) を使って、プリミティブなデータ型を「アトミック」として扱います。
  • アクセスを制御するために、ミューテックスのような明示的な同期プリミティブを使用する。この場合、コンパイラは助けられないので、自分の行いを把握する必要があります。
  • 気にしない - このアプローチは可能ですが、グローバル変数の正確な値が本当に重要ではない場合にのみ意味があります。例えば、rand モジュールでは、グローバル変数を使って(暗号ではない)疑似乱数を生成しています。この場合、データレースにより、異なるスレッドの乱数が多少相関することになりますが、同期化プリミティブを使用することによるパフォーマンスの低下を考慮すると、これは許容範囲内です。

C コンパイラフラグを渡す

#flag ディレクティブを V ファイルの始めに加えることで、以下のような C コンパイルフラグを指定できます。

  • -I で C インクルードファイルの探索パスを追加
  • -l でリンクしたい C ライブラリの名前を追加
  • -L で C ライブラリファイルの探索パスを追加
  • -D でコンパイル時変数の設定

ターゲットによって異なるフラグが使用できます。現在 linuxdarwinwindows をサポートしています。

注意: (現在は) 以下のように、一行につきフラグ一つを書く必要があります。

#flag linux -lsdl2
#flag linux -Ivig
#flag linux -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
#flag linux -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1
#flag linux -DIMGUI_IMPL_API=

コンソールのビルドコマンドでは、以下のようにできます。

  • -cflags はバックエンド C コンパイラにカスタムフラグを渡します。
  • -cc は、デフォルトの C バックエンドコンパイラを変更します。
  • 例えば、-cc gcc-9 -cflags -fsanitize=thread のようになります。

ターミナルで VFLAGS 環境変数を定義して、-cc-cflags の設定を保存できます。

#pkgconfig

#pkgconfig ディレクティブを追加すると、それぞれの依存関係で提供される pkg-config ファイルを使って、コンパイルやリンクに使うモジュールをコンパイラに伝えることができます。

V では #flag でバッククォートが使用できず、プロセスを spawn することがセキュリティや移植性の観点から望ましくないため、標準 freedesktop と互換性のある独自の pkgconfig ライブラリを使用しています。

これにフラグが渡されなかった場合は、以下のような --cflags--libs を追加します。

#pkgconfig r_core
#pkgconfig --cflags --libs r_core

.pc ファイルは、ハードコードされた pkg-config のデフォルトパスのリストを参照します。複数のモジュールを渡すこともできます。

pkg-config が存在するかどうかを確認するには、コンパイル時の if 条件として $pkgconfig('pkg') を使用します。存在すればその節が作成されます。それ以外の場合は、$else$else $if で対応します。

$if $pkgconfig('mysqlclient') {
    #pkgconfig mysqlclient
} $else $if $pkgconfig('mariadb') {
    #pkgconfig mariadb
}

C コードのインクルード

V モジュールの中に C コードを追加することもできます。例として、C のコードがモジュールフォルダ内の 'c' という名前のフォルダにあるとすると、以下のような手順で追加します。

  • モジュールの最上位フォルダに v.mod ファイルを配置します (v create でモジュールを作成した場合はすでにあります)。これは、以下のようになっています。
Module {
    name: 'mymodule',
    description: 'My nice module wraps a simple C library.',
    version: '0.0.1'
    dependencies: []
}
  • モジュールの一番上にこれらの行を加えます。
#flag -I @VROOT/c
#flag @VROOT/c/implementation.o
#include "header.h"

注意: @VROOT は、 V によって v.mod ファイルが存在する一番近い親フォルダに置き換えられます。v.mod ファイルがあるフォルダと同じかそれ以下に存在する .v ファイルには、#flag @VROOT/abc を使うとそのフォルダを参照するようにできます。@VROOT フォルダはモジュールのルックアップパスの前に 付加されている ので、@VROOT フォルダ下に他のモジュールをインポートできます。

上の手順では、モジュールフォルダ/c/implementation.o へコンパイルされた .o ファイルを V に探してもらっています。V が見つけた場合、.o ファイルはモジュールを使用するメイン実行ファイルにリンクされます。見つからなければ、V は @VROOT/c/implementation.c ファイルがあると仮定して、それを .o ファイルにコンパイルして使おうとします。

これで、簡単に V モジュールへ C 言語のコードを同梱して配布できるようになります。V のラッパーモジュールで C コードを使用している方向けに、完全なサンプル 最小でモジュールを使った C コードのある V プロジェクト をご用意しています。他の例としましては、C から V へ構造体を渡して戻すデモ C から V から C への相互運用 もあります。

-cflags を使用すると、カスタムフラグをバックエンドの C コンパイラに渡すことができます。また、-cc を使用してデフォルトの C バックエンドコンパイラを変更することもできます。例えば、-cc gcc-9 -cflags -fsanitize=thread のようになります。

C の型

よくあるゼロ終端の C 文字列は unsafe { charptr(cstring).vstring() }、 長さが分かっている場合は unsafe { charptr(cstring).vstring_with_len(len) で V 文字列に変換できます。

注意: .vstring().vstring_with_len()cstring のコピーを作成 しない ので、.vstring() を呼び出した後にそれを解放しないでください。C 文字列のコピーを作成する必要がある場合 (getenv のような libc API のいくつかは、libc の内部メモリへのポインタを返すので大抵こうする必要がある)、cstring_to_vstring(cstring) のようにします。

Windows では、C API が wide 文字列 (UTF-16 エンコーディング) を返すことがあります。これらは string_from_wide(&u16(cwidestring)) で V 文字列に変換できます。

C との相互運用性を高めるため、V には以下の型があります。

  • C の void* である voidptr
  • C の byte* である &byte
  • C の char* である &char
  • C の char** である &&char

voidptr を V の参照にキャストするには、user := &User(user_void_ptr) というふうにします。

voidptr は、user := User(user_void_ptr) のようにキャストによって V 構造体へ参照外しすることもできます。

V から C コードを呼び出す例であれば、socket.v を確認してみてください

C 宣言

C 言語の識別子にアクセスするには、モジュール固有の識別子にアクセスするのと同様に、C という接頭辞を付けます。関数は使用する前に V で再宣言する必要があります。どんな C 言語の型でも C というプレフィックスを付けて使用できますが、型のメンバーにアクセスするには V で型を再宣言する必要があります。

複雑な型を再宣言するには、以下の C コードのようにします。

struct SomeCStruct {
    uint8_t implTraits;
    uint16_t memPoolData;
    union {
        struct {
            void* data;
            size_t size;
        };

        DataView view;
    };
};

サブデータ構造のメンバーは、以下のように中の構造体で直接宣言できます。

struct C.SomeCStruct {
    implTraits  byte
    memPoolData u16
    // これらのメンバーは、現在Vでは表現できないサブデータ構造の一部です。
    // このように直接宣言すれば、アクセスするには十分です。
    // union {
    // struct {
    data voidptr
    size size_t
    // }
    view C.DataView
    // }
}

データメンバーの存在が V に知らされれば、元の構造を正確に再現することなく使用できます。

また、サブデータ構造を 埋め込んで 並列コード構造を維持することもできます。

デバッグ

C バックエンドバイナリ (既定)

生成されたバイナリ (フラグ: -b c) の問題をデバッグするには、以下のフラグを渡すことができます。

  • -g - より多くのデバッグ情報を含む、最適化なしの実行ファイルを生成します。通常、V に -g が渡されると、パニック時に生成するスタックトレースに、.v ファイルの行番号を表示するよう実行ファイルに強制します。低レベルのコードを書いている場合には、次のオプション -cg を使用してください。
  • cg- より多くのデバッグ情報を含む、最適化されていない実行ファイルを生成します。この場合、実行ファイルは C 言語のソース行番号を使用します。パニックが発生したときに生成された C プログラムを確認したり、デバッガ (gdblldbなど) で生成された C ソースコードを表示できるようにするために、-keepc` と組み合わせてよく使われます。
  • -showcc - プログラムのビルドに使用された C コマンドを表示します。
  • -show-c-output - プログラムのコンパイル時に C コンパイラが生成した出力を表示します。
  • -keepc - コンパイルが成功した後も、生成された C ソースコードファイルを削除しません。また、同じファイルパスを使い続けることで、より安定し、エディタや IDE で開いたままにしておくことができます。

よりよいデバッグをするには、v -cg -keep_c -pretty_c -show_c_cmd yourprogram.v のように同時にすべて渡すとよいでしょう。そして、生成された実行形式に対してデバッガ (gdb/lldb) や IDE を実行するだけです。

生成された C 言語のコードを、それ以上コンパイルせずに検査したいだけならば、-o file.c という方法もあります。こうすると、V は file.c を生成して停止します。

main のような単一の C 関数に対して生成された C のソースコードを見たい場合は、-printfn main -o file.c としてください。

V の実行ファイル自体をデバッグするには、ソースから ./v -g -o v cmd/v でコンパイルする必要があります。

テストをデバッグするには、例えば v -g -keepc prog_test.v のようにします。-keepc フラグが必要なのは、実行ファイルが作成されて実行された後、削除されないようにするためです。

V がサポートする全フラグの詳細なリストは、v helpv help buildv help build-c を参照してください。

コマンドラインでのデバッグ
  1. デバッグ情報付きでバイナリをコンパイルします v -g hello.v
  2. lldbGDB でデバッグします lldb hello

トラブルシューティング (デバッグ) は GDB で V が作成した実行ファイルを

視覚的なデバッグのセットアップ:

ネイティブバックエンドバイナリ

現在、ネイティブバックエンド (フラグ: -b native) で作成されたバイナリのデバッグはサポートされていません。

JavaScript バックエンド

生成された JavaScript の出力をデバッグするために、ソースマップを有効化できます。v -b js -sourcemap hello.v -o hello.js

サポートされているすべてのオプションについては、最新のヘルプを確認してください。v help build-js

条件付きコンパイル

コンパイル時のコード

$ はコンパイル時操作の接頭辞として使われます。

$if 条件

// 一つの節で複数の条件をサポート
$if ios || android {
    println('Running on a mobile device!')
}
$if linux && x64 {
    println('64-bit Linux.')
}
// 式として利用
os := $if windows { 'Windows' } $else { 'UNIX' }
println('Using $os')
// $else-$if 節
$if tinyc {
    println('tinyc')
} $else $if clang {
    println('clang')
} $else $if gcc {
    println('gcc')
} $else {
    println('different compiler')
}
$if test {
    println('testing')
}
// v -cg ...
$if debug {
    println('debugging')
}
// v -d option ...
$if option ? {
    println('custom option')
}

コンパイル時に評価される分岐をしたい場合、$ が前に付いた if を使います。現在は OS または -debug コンパイラ引数の検出にしか使用できません。

コンパイル時に if を評価したい場合は、前に $ を付ける必要があります。現在は、OS、コンパイラ、プラットフォーム、コンパイルオプションを検出するために使われています。$if debug$if windows$if x32 のような特別なオプションです。自作の ifdef を使うには、$if option ? {} と書いて、v -d option のようにしてコンパイルする必要があります。以下はビルトインオプションの完全なリストです。

OS Compilers Platforms Other
windows, linux, macos gcc, tinyc amd64, aarch64 debug, test, js
mac, darwin, ios clang, mingw x64, x32 glibc, prealloc
android, mach, dragonfly msvc little_endian no_bounds_checking
gnu, hpux, haiku, qnx cplusplus big_endian
solaris, linux_or_macos

$embed_file

module main
fn main() {
    embedded_file := $embed_file('v.png')
    mut fw := os.create('exported.png') or { panic(err) }
    fw.write_bytes(embedded_file.data(), embedded_file.len)
    fw.close()
}

V ではコンパイル時に $embed_file(<path>) を呼び出して任意のファイルを実行ファイルに埋め込むことができます。パスはソースファイルに対する絶対パスまたは相対パスを指定できます。

-prod を使用しない場合、ファイルは埋め込まれません。その代わり、実行時に f.data() を最初に呼び出したときに読み込まれます。これにより外部エディタプログラムでの変更が容易になり、実行ファイルを再コンパイルする必要がなくなります。

-prod でコンパイルすると、ファイルは実行ファイルの 中に埋め込まれ ます。バイナリサイズは大きくなりますが、より自己完結的で配布が容易になります。この場合の f.data()IO を発生させず、常に同じデータを返します。

$tmpl で V テンプレートファイルの埋め込みとパース

V にはテキストや html テンプレート用のシンプルなテンプレート言語があります。これは、$tmpl('path/to/template.txt') で簡単に埋め込むことができます。

fn build() string {
    name := 'Peter'
    age := 25
    numbers := [1, 2, 3]
    return $tmpl('1.txt')
}

fn main() {
    println(build())
}
1.txt
name: @name

age: @age

numbers: @numbers

@for number in numbers
  @number
@end

出力:

name: Peter

age: 25

numbers: [1, 2, 3]

1
2
3

$env

module main

fn main() {
    compile_time_env := $env('ENV_VAR')
    println(compile_time_env)
}

V はコンパイル時に環境変数から値を取り込むことができます。$env('ENV_VAR') は、#flag linux -I $env('JAVA_HOME')/include のようにトップレベルの #flag#include 文でも使用できます。

環境固有ファイル

ファイル名に環境固有指定のサフィックスが付いている場合は、その環境でのみコンパイルされます。

  • .js.v => JS バックエンドでのみ使用されます。これらのファイルには JS コードを含められます。
  • .c.v => C バックエンドでのみ使用されます。これらのファイルには C コードを含められます。
  • .x64.v => V の x64 バックエンドでのみ使用されます。
  • _nix.c.v => Unix システム (非 Windows) バックエンドでのみ使用されます。
  • _${os}.c.v => 特定 OS システムでのみ使用されます。例えば _windows.c.v は Windows 上でコンパイルする場合または -os windows でコンパイルする場合にのみ使用されます。
  • _default.c.v => 特定プラットフォームのファイルがない場合にのみ使用されます。例えば file_linux.c.vfile_default.c.v の両方があって Linux 向けにコンパイルしている場合、file_linux.c.v だけが使用されて file_default.c.v は無視されます。

以下はより完全なサンプルです。

main.v
module main
fn main() { println(message) }
main_default.c.v
module main
const ( message = 'Hello world' )
main_linux.c.v
module main
const ( message = 'Hello linux' )
main_windows.c.v
module main
const ( message = 'Hello windows' )

上記のサンプルは以下のようになっています。

  • Windows 向けにコンパイルすると、'Hello windows' が

  • Linux 向けにコンパイルすると、'Hello linux' が

  • 他のプラットフォーム向けにコンパイルすると、固有のものではない 'Hello world' メッセージが表示されます。

  • _d_customflag.v => これは $if customflag ? {} に相当しますが、単一のブロックではなくファイル全体に対して使用されます。customflag は snake_case の識別子でなければならず、任意の文字を含むことはできません (小文字のラテン文字 + 数字 + _ のみ)。注意: _d_customflag_linux.c.v のような接尾子の組み合わせは動作しません。プラットフォーム依存のコードを含むカスタムフラグファイルが必要な場合は、_d_customflag.v 接尾子を使用し、その中で $if linux {} などのようなプラットフォーム依存のコンパイル時分岐ブロックを使用します。

コンパイル時疑似変数

V はまた、コンパイル時に置換される疑似文字列変数セットへのアクセスも提供します。

  • @FN => 現在の V 関数の名前に置換
  • @MOD => 現在の V モジュールの名前に置換
  • @STRUCT => 現在の V 構造体の名前に置換
  • @FILE => この V ソースファイルのパスに置換
  • @LINE => これが (文字列としての) ソースファイルに表れている V 行数に置換
  • @COLUMN => これが (文字列としての) ソースファイルに表れているその行での文字数に置換
  • @VEXE => V コンパイラへのパスに置換
  • @VHASH => V コンパイラの短縮コミットハッシュ (文字列) に置換
  • @VMOD_FILE => 最も近い位置の v.mod ファイルの内容 (文字列) に置換

これらによって以下の例のようなことができ、コードのデバッグ/ログ/トレースに便利です。

eprintln( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @MOD + '.' + @FN)

以下に別の例として、v.mod のバージョン/名前を実行ファイルの中に埋め込みたい場合を示します。

import v.vmod
vm := vmod.decode( @VMOD_FILE ) or { panic(err) }
eprintln('$vm.name $vm.version\n $vm.description')

パフォーマンス最適化

生成された C 言語のコードは、-prod を使ってコンパイルすれば、通常は十分に高速です。しかし、状況によっては、C コンパイラに追加のヒントを与えて、コードブロックをさらに最適化できるようにしたい場合もあるでしょう。

注意: これはめったに必要なく、使うべきでもありません。プロファイリングした結果、大きなメリットがあることが確認できれば構いませんが。
gcc のドキュメントから引用すると、"プログラマは書いたプログラムが実際にどう動くのか予測しがたいものだ。"

[inline] - 関数に [inline] タグを付けることができるので、C コンパイラはそれらをインライン化しようとします。大抵の場合パフォーマンスが改善しますが、実行形式のサイズは膨らみます。

[direct_array_access] - [direct_array_access] のタグを付けた関数では、コンパイラが配列操作を直接 C 言語の配列操作に変換します。これはその関数における配列の反復処理時間を大幅に節約できますが、その代償としてユーザが境界を確認しない限り関数を非安全にしてしまうことになります。

if _likely_(真偽値の式) { は C コンパイラのヒントで、渡された真偽値の式が真である可能性が非常に高いことを示しています。JS バックエンドでは何もしません。

if _unlikely_(真偽値の式) {likely_(x) に似ていますが、真偽値の式が偽である可能性が高いことを示唆しています。JS バックエンドでは何もしません。

コンパイル時リフレクション

組み込み JSON サポートはありますが、V では以下のように任意のものに対する効果的なシリアライザを作成することもできます。

// TODO: 実装は不完全

struct User {
    name string
    age  int
}

// 注: T には構造体名だけを渡すようにします
fn decode<T>(data string) T {
    mut result := T{}
    // コンパイル時 `for` ループ
    // T.fields は、フィールドメタデータ型の配列です
    for field in T.fields {
        if field.typ == 'string' {
            // $(string_expr) はその識別子になります
            result.$field = get_string(data, field.name)
        } else if field.typ == 'int' {
            result.$field = get_int(data, field.name)
        }
    }
    return result
}

// `decode<User>` で以下が生成されます
fn decode_User(data string) User {
    mut result := User{}
    result.name = get_string(data, 'name')
    result.age = get_int(data, 'age')
    return result
}

制限付き演算子オーバーロード

struct Vec {
    x int
    y int
}

fn (a Vec) str() string {
    return '{$a.x, $a.y}'
}

fn (a Vec) + (b Vec) Vec {
    return Vec {
        a.x + b.x,
        a.y + b.y
    }
}

fn (a Vec) - (b Vec) Vec {
    return Vec {
        a.x - b.x,
        a.y - b.y
    }
}

fn main() {
    a := Vec{2, 3}
    b := Vec{4, 5}
    println(a + b) // "{6, 8}"
    println(a - b) // "{-2, -2}"
}

演算子オーバーロードは、V のシンプルさと可視性の哲学に反しています。しかし科学的かつ視覚的なアプリケーションは V の得意分野なので、演算子オーバーロードは以下のような可読性の向上のためにとても重要です。

a.add(b).add(c.mul(d))a + b + c * d と比べて読みづらいですしね。

安全性と保守性を促進するため、演算子オーバーロードには以下のようないくつかの制限があります。

  • オーバーロードできるのは +, -, *, /, %, <, >, ==, !=, <=, >= の演算子だけです。
  • ==!= はコンパイラによって自己生成されますが、オーバーライドできます。
  • 演算子関数の中で他の関数を呼び出せません。
  • 演算子関数の引数は変更できません。
  • <==演算子を使用する場合、戻り値の型はbool` でなければなりません。
  • !=><=>= は、==< が定義されているときに自動生成されます。
  • 両方の引数は同じ型でなければなりません(V のすべての演算子と同じです)。
  • 代入演算子 (*=+=/= など) は、演算子が定義されているときに自動生成されますが、同じ型を返さなければなりません。

インラインアセンブリ

fn main() {
    a := 10
    asm x64 {
        mov eax, [a]
        add eax, 10
        mov [a], eax
    }
}

さらなる例は、https://github.com/vlang/v/tree/master/vlib/v/tests/assembly/asm_test.amd64.v をご覧ください。

C を V へ変換する

TODO: C から V への変換は V 0.3 から利用できます。

V は C のコードを可読性のある V のコードへ変換し、C ライブラリをラップした V を生成します。

まずは簡単なプログラム test.c を作成してみましょう。

test.cpp
#include "stdio.h"

int main() {
    for (int i = 0; i < 10; i++) {
        printf("hello world\n");
    }
    return 0;
}

v translate test.c を実行すると、V は test.v を生成します。

test.v
fn main() {
    for i := 0; i < 10; i++ {
        println('hello world')
    }
}

C ライブラリのラッパを生成するには、以下のコマンドを使用します。

v wrapper c_code/libsodium/src/libsodium

これにより V モジュールに libsodium ディレクトリが生成されます。

以下は C から V へ生成された libsodium のラッパのサンプルです。

C コードを翻訳したり、V から C コードを呼び出したりする判断はどのようにするべきでしょうか?

よく書かれていてよくテストされている C コードであれば、もちろん単に V からこの C コードを呼び出せます。

V へ翻訳するメリットを以下にいくつか述べます。

  • そのコード基盤を開発する予定の場合、一つの言語ですべてできているので、C でそれを開発するよりは安全で簡単です。
  • クロスコンパイルがもっと簡単になります。なにも心配することはありません。
  • ビルドフラグとインクルードファイルはもうありません。

ホットコードリロード

message.v
module main

import time
import os

[live]
fn print_message() {
    println('Hello! Modify this message while the program is running.')
}

fn main() {
    for {
        print_message()
        time.sleep_ms(500)
    }
}

この例を v -live message.v でビルドします。

リロードされてほしい関数には、その定義の手前に [live] 属性をつける必要があります。

現在は実行中に型を変更することはできません。

他に、グラフィカルアプリケーションの例があります。
https://github.com/vlang/v/tree/master/examples/hot_code_reloading

クロスコンパイル

プロジェクトをクロスコンパイルするには、単に

v -os windows .

v -os linux .

を実行します。(macOS 向けのクロスコンパイルは現在は不可能です。)

C 依存ファイルがなければ、それだけなんです。これは、ui モジュールを使用した GUI アプリや、gg を使用したグラフィカルアプリをコンパイルするときも動作します。

Windows や Linux では、Clang、LLD リンカをインストールし、ライブラリとインクルードファイルの入った zip ファイルをダウンロードする必要があります。 V ではそのリンクを提供しています。

V でクロスプラットフォームなシェルスクリプト

V は Bash の代替として、デプロイスクリプト、ビルドスクリプトなどを書くことに使用できます。

このために V を使うメリットは、言語のシンプルさ、読みやすさ、クロスプラットフォームサポートです。「V スクリプト」は UNIX 系システム上でも Windows と同じようにうまく動作します。

プログラムを .vsh という拡張子にすると、os モジュール内のすべての関数がグローバルになります (例えば os.mkdir() の代わりに mkdir() が使えます)。

以下はサンプルの deploy.vsh です。

deploy.vsh
#!/usr/local/bin/v run
// 上のシバンは、Unix 系システムでファイルを V に関連付け、
// `chmod +x` で実行可能にしてからファイルパスを指定すれば
// 実行できるようにしています。

// build/ が存在すれば取り除き、無ければエラーを無視します
rmdir_all('build') or { }

// build/ を作成し、これは build/ が存在しないため失敗しません
mkdir('build') ?

// *.v ファイルを build/ へ移動します
result := exec('mv *.v build/') ?
if result.exit_code != 0 {
    println(result.output)
}
// 以下と同じ:
// files := ls('.') ?
// mut count := 0
// if files.len > 0 {
//     for file in files {
//         if file.ends_with('.v') {
//              mv(file, 'build/') or {
//                  println('err: $err')
//                  return
//              }
//         }
//         count++
//     }
// }
// if count == 0 {
//     println('No files')
// }

これで、これを通常の V プログラムのようにコンパイルし、どこでもデプロイし実行できる実行形式が得られます。

v deploy.vsh && ./deploy

もしくは伝統的な bash スクリプトのようにそのまま実行します。

v run deploy.vsh

Unix 系プラットフォームでは、chmod +x で実行可能にすれば ./deploy.vsh でファイルを直接実行できます。

属性

Vには、関数や構造体の動作を変更する属性がいくつかあります。

属性は、関数/構造体の宣言の直前の [] 内で指定するとその定義にのみ適用されます。

// この関数を呼び出すと非推奨の警告が出ます
[deprecated]
fn old_function() {
}

// カスタムの非推奨メッセージも表示できます
[deprecated: 'use new_function() instead']
fn legacy_function() {}

// また、日付を指定することもでき、その日付以降、
// その関数は廃止されたとみなされます。その日より前での
// その関数の呼び出しは、コンパイラ通知として表示されますが、
// コンパイルには影響しません。その日以降は呼び出しが警告に
// なるため、通常のコンパイルは動作しますが、-prod での
// コンパイルは動作しません (-prod ではすべての警告が
// エラーとして扱われます)。非推奨日から 6 ヶ月後には、
// 呼び出しはハードコンパイラエラーになります。
[deprecated: 'use new_function2() instead']
[deprecated_after: '2021-05-27']
fn legacy_function2() {}

// この関数の呼び出しはインライン化されます。
[inline]
fn inlined_function() {
}

// この関数の呼び出しはインライン化されません。
[noinline]
fn function() {
}

// この関数は呼び出し元に戻りません。
// このような関数は、exit/1 や panic/1 のように、or ブロックの最後に使用することができます。 このような関数は戻り値を持つことができず、for{} で終わるか、他の `[noteturn]` 関数を呼び出して終了しなければなりません。
[noreturn]
fn forever() {
    for {}
}

// 次の構造体はヒープ上に割り当てられなければなりません。そのため、参照 (`&Window`) として、または別の参照 (`&OuterStruct{ Window{...} }`) の内部でのみ使用できます。
// 「スタックとヒープ」の章もご覧ください。
[heap]
struct Window {
}

// フラグが false になっていると、この関数とその呼び出しのコードを生成しなくなります。
// フラグを使うには、`v -d flag` のようにします
[if debug]
fn foo() { }

fn bar() {
   foo() // `-d debug` が渡されていないと呼び出されない
}

// この関数のポインタ引数が指すメモリは、この関数が戻る前にガベージコレクタによって
// 解放されません (使用されている場合)。
[keep_args_alive]
fn C.my_external_function(voidptr, int, voidptr) int

// 以下の関数は unsafe{} ブロック内で呼び出さなければなりません。
// なお、`risky_business()` 本体のコードは、`unsafe {}` ブロックで
// 包まない限りチェックされます。
// これは、ある安全ではない操作の前後にチェックを行う `[unsafe]` 関数を
// 作りたいときに便利で、V の安全機能の恩恵も受けられます。
[unsafe]
fn risky_business() {
    // チェックコード、おそらく事前条件のチェック
    unsafe {
        // ポインタ演算、ユニオンフィールドへのアクセス、他の `[unsafe]` 関数の呼び出し
        // などがチェック *されない* コードです。
        // 通常は、unsafe{} で囲まれたコードをできるだけ少なくするようにするのがよいでしょう。
        // [メモリ安全でないコード](#メモリ安全でないコード) もご覧ください
    }
    // チェックコード、おそらく事後条件のチェックや不変性の保持
}

// V の autofree エンジンはこの関数でメモリ管理を行いません。
// あなたは、その中でメモリの解放を手動で行う責任があります。
[manualfree]
fn custom_allocations() {
}

// C との相互運用向けのみですが,この構造体が C 内で `typedef struct` によって定義されていると V に伝えます。
[typedef] 
struct C.Foo {
}

// Win32 API コードにコールバック関数を渡す必要がある時に使用します
[windows_stdcall]
fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)

// Windows 専用:
// デフォルトのグラフィックライブラリ (gg、ui など) がインポートされている場合、グラフィカルウィンドウ
// が優先され、コンソールウインドウは作成されず、println() ステートメントが効果的に無効になります。
// コンソールウインドウを明示的に作成する際にこれを使用します。main() の前でのみ有効です。
[console]
fn main() {
}

Goto

V では、goto でラベルへ無条件ジャンプできます。ラベル名は goto 文と同じ関数内に含まれている必要があります。プログラム中で現在のスコープの外側またはそれよりも深い場所にあるラベルへ goto できますが、変数の初期化を飛ばしてはいけません。

if x {
    // ...
    if y {
        goto my_label
    }
    // ...
}
my_label:

goto は避けて、できる限り for を使用するべきです。特に、ラベル付き break はネストしたループからの脱出に利用できます。

補遺

補遺 I: キーワード

V には 41 の予約キーワード (うち 3 個はリテラル) があります。

as
asm
assert
atomic
break
const
continue
defer
else
embed
enum
false
fn
for
go
goto
if
import
in
interface
is
lock
match
module
mut
none
or
pub
return
rlock
select
shared
sizeof
static
struct
true
type
typeof
union
unsafe
__offsetof

もご参照ください。

補遺 II: 演算子

このリストの演算子は プリミティブ型 専用です。

記号 意味 オペランド
+ integers, floats, strings
- integers, floats
* integers, floats
/ integers, floats
% 剰余 integers
~ ビット NOT integers
& ビット AND integers
| ビット OR integers
^ ビット XOR integers
! 論理 NOT bools
&& 論理 AND bools
|| 論理 OR bools
!= 論理 XOR bools
<< 左シフト integer << unsigned integer
>> 右シフト integer >> unsigned integer

優先度

優先度 演算子
5 * / % << >> &
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

代入演算子

+=   -=   *=   /=   %=
&=   |=   ^=
>>=  <<= 
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
161
Help us understand the problem. What are the problem?