Help us understand the problem. What is going on with this article?

V プログラミング言語

はじめに

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

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

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

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

以下から訳文


V ドキュメント

導入

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

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

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

シンプルですが、開発者には大きな力を与えます。
他の言語でできることなら、V もできます。

Hello World

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

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

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

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

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

上記の例の通り、関数は fn で宣言します。
戻り値の型は関数名の後ろに来ます。この場合 main は何も返さないので型は省かれています。

C やその類の言語と同じく、main はエントリーポイントです。

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

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

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

println('hello world')

コメント

// これは単行コメント。

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

関数

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

関数は複数の値を返すことができます。

pub fn public_function() {
}

fn private_function() {
}

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

変数

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

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

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

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

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

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

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

:== の違いに注意してください。
:= は宣言と初期化に用いられますが、= は代入に用いられます。

fn main() {
    age = 21
}

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

fn main() {
    age := 21
}

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

fn main() {
    a := 10
    if true {
        a := 20
    }
}

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

基本の型

bool

string

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

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

f32 f64

any_int, any_float // 内部で用いる数値リテラルの中間表現型

byteptr, voidptr, charptr, size_t // これらは C との相互運用のためのもの

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

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

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

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

例えば、int の値は自動的に f64i64 に拡大変換できますが、f32u32 にはなりません (f32 は大きい値の精度が低下するためで、u32 は負値の符号を失うためです)。

(訳注: おそらく記述ミス、図によると f32 なってしまうようだが...?)

文字列

name := 'Bob'
println('Hello, $name!') // `$` は文字列挿入に使う
println(name.len)

bobby := name + 'by' // + は文字列連結に使う
println(bobby) // "Bobby"

println(bobby[1:3]) // "ob"
mut s := 'hello '
s += 'world' // `+=` は文字列追加に使う
println(s) // "hello world"

V では、文字列は読み出し専用のバイトの配列です。文字列データは UTF-8 エンコードされます。

文字列はイミュータブルです。

文字列を表すのに、シングルクォートとダブルクォートの両方が使用できます。一貫性のため、vfmt はシングルクォート文字を含まない限りダブルクォートをシングルクォートに変換します。

挿入構文はだいぶシンプルです。このように、'age = $user.age' とすればフィールドでも動きます。より複雑な式が必要であれば、${} を用いて 'can register = ${user.age > 13}' というようにします。

V の演算子は両辺の値が同じ型でなければなりません。この以下のコードは ageint の場合はコンパイルされません。

println('age = ' + age)

以下のように age を文字列に変換するか、

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

以下の文字列挿入をします (こちらを推奨)。

println('age = $age')

文字リテラルを表す場合は、` を使用します。

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

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

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

配列

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

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` は文字列の配列です。 
println(names.len) // "3" 
println('Alex' in names) // "false" 

names = [] // 配列はこれで空になる

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

// 要素を一定量だけ事前に確保することもできます。
ids := []int{ len: 50, init: 0 } // これは 50 個のゼロが入った配列を作成します

配列の型はその最初の要素で決定されます。[1, 2, 3] は整数の配列 ([]int) です。

['a', 'b'] は文字列の配列 ([]string) です。

V が配列の方を推定できない時は、[byte(0x0E), 0x1F, 0xBA, 0x0E] のようにユーザが最初の要素の型を明示することができます。

V の配列はホモジニアス (すべての要素が同じ型) でなければなりません。[1, 'a'] はコンパイルされません。

<< は配列の末尾に値を追加する演算子です。他の配列全体を追加することもできます。

.len フィールドは配列の長さを返します。注意として、これは読み出し専用のフィールドで、ユーザーによって変更できません。V ではすべての外から見えるフィールドはデフォルトで読み出し専用です。

val in array は配列に val が含まれていれば true を返します。

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

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

nums := [1, 2, 3, 4, 5, 6]
even := nums.filter(it % 2 == 0)
println(even) // [2, 4, 6]

words := ['hello', 'world']
upper := words.map(it.to_upper())
println(upper) // ['HELLO', 'WORLD']

itfilter / map メソッド内での要素を表す特殊な変数です。

辞書配列

mut m := map[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')

// 短縮構文
numbers := {
    'one': 1,
    'two': 2,
}

インポート

import os

fn main() {
    println('Enter your name:')
    name := os.get_line()
    println('Hello, $name!')
}

モジュールはキーワード import を使用してインポートすることができます。他のモジュールから型、関数、定数を使用する場合は、完全なパスを指定する必要があります。上の例だと、name := get_line() では動作しません。つまり、関数がどのモジュールから呼び出されたのかが常に明確であるということです。

文と式

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"

In 演算子

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

nums := [1, 2, 3]
println(1 in nums) // true 

m := {'one': 1, 'two': 2}
println('one' in m) // true

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

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 しかありません。

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

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

mut numbers := [1, 2, 3, 4, 5]
for i, num in numbers {
    println(num)
    numbers[i] = 0
}
mut sum := 0
mut i := 0
for i < 100 {
    sum += i
    i++
}
println(sum) // "5050"

このループの書き方は他の言語での while ループと同様です。

このループは条件式が false に評価されると繰り返しを止めます。

同じく、条件式は括弧で囲まず、波括弧は常に必要です。

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

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

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

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

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

Match

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

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

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

enum Color {
    red
    blue
    green
}

fn is_red_or_blue(c Color) bool {
    return match c {
        .red  { true  }
        .blue { true  }
        else  { false }
    }
}

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

defer

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

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

構造体

struct Point {
    x int
    y int
}

p := Point{
    x: 10
    y: 20
}

println(p.x) // 構造体のフィールドはドットでアクセスできます

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

p := &Point{10, 10}  // 3 つ以下のフィールドの構造体を初期化する、もう一つの構文
println(p.x) // 参照にもフィールドにアクセスする構文がある  

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

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

// TODO: これは後に実装されます
struct Button {
    Widget
    title string
}

button := new_button('Click me')
button.set_pos(x, y)

// 埋め込まれていないとこうすることになる
button.widget.set_pos(x, y)
struct Foo {
    n   int      // n は 0 がデフォルト値
    s   string   // s は '' がデフォルト値
    a   []int    // a は `[]int{}` がデフォルト値
    pos int = -1 // カスタムがデフォルト値
}

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

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

構造体初期化の短縮構文

関数にデフォルト引数や名前付き引数はありませんが、以下のような構造体初期化の短縮構文はあります。

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 を指定していないので、デフォルト値の 20 になります

ご覧の通り以下のようにできます。

new_button(text:'Click me', width:100)

こうする必要はありません。

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 byteptr
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 のようなレシーバー名は使わずに、短めで、一文字長の名前が好まれます。

関数 2

既定で純粋関数

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

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

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

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

struct User {
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 の前に追加してこの関数を呼び出す必要があります。これは呼び出す関数が値を変更することを明確にします。

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

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

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

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

user = register(user) 

無名 & 高階関数

fn sqr(n int) int {
    return 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
    })
}

参照

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

fn bar_function(foo Foo) {
    ...
}

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

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

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

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

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

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

定数

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

println(pi)
println(world)

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

定数は変更できません。

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

struct Color {
        r int
        g int
        b int
}

fn (c Color) str() string { return '{$c.r, $c.g, $c.b}' }

fn rgb(r, g, 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)
)

fn main() {
        println(numbers)
        println(red)
        println(blue)
} 

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

println('Top cities: $TOP_CITIES.filter(.usa)')
// または
println('Top cities: $top_cities.filter(.usa)')

println

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

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 メソッドを定義するだけです。

改行したくない場合は、代わりに print() を使いましょう。

モジュール

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

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

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

mymodule/ には好きなだけ .v ファイルを入れることができます。

それでは、これをコード内で使ってみます。

module main

import mymodule

fn main() { 
    mymodule.say_hi()
}

注意として外部の関数を呼ぶには毎回モジュールを指定する必要があります。これは煩わしく思えるかもしれませんが、コードはより可読性があり理解しやすいようになります。どのモジュールによるどの関数が呼ばれているのか常に明らかだからです。特に巨大なコードにおいては。

モジュールの名前は 10 文字以下くらいに短くするべきです。循環インポートは許可されていません。

今現在はモジュールをどこにでも作成できます。

すべてのモジュールは静的に単一の実行形式にコンパイルされます。

インポートされた場合に何らかのセットアップ/初期化コードを自動的に呼び出してほしい (専ら C ライブラリの関数を呼び出したい) 場合、以下のようなモジュールの init 関数をそのモジュール内に書きます。

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

init 関数は公開できません。自動的に呼び出されます。

型 2

インターフェイス

struct Dog {}
struct Cat {}

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

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

interface Speaker {
    speak() string
}

fn perform(s Speaker) string {
    return s.speak()
}

dog := Dog{}
cat := Cat{}
println(perform(dog)) // "woof"
println(perform(cat)) // "meow"

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

列挙体

enum Color {
    red green blue
}

mut color := Color.red
// V は `color` が `Color` 型だとわかっています。ここで `color = Color.green` とする必要はありません。
color = .green
println(color) // "1"  TODO: "green" と出力する?

直和型

type Expr = BinaryExpr | UnaryExpr | IfExpr

struct BinaryExpr{ ... }
struct UnaryExpr{ ... }
struct IfExpr{ ... }

struct CallExpr {
    args []Expr
    ...
}

fn (mut p Parser) expr(precedence int) Expr {
    match p.tok {
        .key_if { return IfExpr{} }
        ...
        else    { return BinaryExpr{} }
    }
}

fn gen(expr Expr) {
    match expr {
        IfExpr { gen_if(expr) } // `expr` is cast to the matched type automatically
        ...
    }
}

fn gen_if(expr IfExpr) {
    ...
}

直和型が実際の型がどうか確かめるには、以下のように is を使用します。

println(expr is IfExpr)

直和型をそのバリアントの型へキャストするには、以下のように as を使用します。

bin_expr := expr as BinaryExpr

また、マッチを使用してバリアントを決定し、同時にキャストすることもできます。match ブランチ内でキャストされたバリアントにアクセスするには、3 つの方法があります。

  • it 変数
  • シャドーイングされた match の変数
  • as で変数名を指定する
fn binary_expr(bx BinaryExpr) {...}
fn unary_expr(ux UnaryExpr) {...}
fn if_expr(ix IfExpr) {...}

// `it` の使用
match expr {
    BinaryExpr { binary_expr(it) }
    ...
}
// シャドーイングされた変数の使用、この場合は `expr`
match expr {
    UnaryExpr { unary_expr(expr) }
    ...
}
// `as` での変数名指定の使用
match expr as actual_expr {
    IfExpr { if_expr(actual_expr) }
    ...
}

注意: シャドーイングは、マッチ式が変数だけの場合にのみ機能します。構造体フィールド、配列の添字指定、キーによる辞書配列の参照などでは機能しません。

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

struct User {
    id int
    name string
}

struct Repo {
    users []User
}

fn new_repo() Repo {
    return Repo {
        users: [User{1, 'Andrew'}, User {2, 'Bob'}, User {10, 'Charles'}]
    }
}

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 := new_repo()
    user := repo.find_user_by_id(10) or { // Option 型は `or` ブロックでハンドルしなければならない
        return // `or` ブロックは `return`, `break`, `continue` のいずれかで終わる必要がある
    } 
    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
}

また、以下のようにエラーを伝播することもできます。

resp := http.get(url)?
println(resp.body)

http.get?http.Response を返します。? を付けて呼び出すので、エラーは呼び出した関数から伝播されます。この場合は main でパニックが起こります。

基本的に上記のコードは以下のコードを短くしたものになります。

resp := http.get(url) or {
    panic(err)
}
println(resp.body)

V には Optional を強制的に取り除く (Rust の unwrap() や Swift の ! のような) 方法はありません。それをするには、代わりに or { panic(err) } を使用します。

ジェネリクス

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)?

並列計算

V の並行処理モデルは Go のものと非常によく似ています。foo() を並列実行するには、go foo() と呼ぶだけです。今のところ、この関数は新しいシステムスレッドで起動しています。近いうちにコアーチンやスケジューラも実装されるでしょう。

Go と違って、V には (まだ) チャンネルがありません。とはいえ、コルーチンと呼び出し側のスレッドの間では、共有変数を使ってデータを交換できます。この変数は、参照として作成して mut としてコルーチンに渡さなければなりません。大元となる struct には、同時アクセスをロックするための mutex も含まれていなければなりません。

import sync

struct St {
mut:
    x int // 共有データ
    mtx &sync.Mutex
}

fn (mut b St) g() {
    ...
    b.mtx.lock()
    // b.x の読み出し/変更/書き込み
    ...
    b.mtx.unlock()
    ...
}

fn caller() {
    mut a := &St{ // 参照として作成したのでヒープに乗ります
        x: 10
        mtx: sync.new_mutex()
    }
    go a.g()
    ...
    a.mtx.lock()
    // a.x の読み出し/変更/書き込み
    ...
    a.mtx.unlock()
    ...
}

JSON のデコード

import json

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 は今日ではとても広く使われているため、組み込みで JSON をサポートしています。

json.decode 関数の第一引数はデコードした型です。第二引数は JSON 文字列です。

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

テスト

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

assrt キーワードはテスト関数の外側でも使用できます。

すべてのテスト関数は <なんらかの名前>_test.v という名前のファイルに配置され、関数名が test_ で始まっている必要があります。

testsuite_begin という特殊なテスト関数を定義することもできます。これは、_test.v ファイル内の他のテスト関数よりも前に実行されます。

testsuite_end という特殊なテスト関数を定義することもできます。これは、_test.v ファイル内の他のテスト関数がすべて実行された後に実行されます。

テストを実行するには v hello_test.v をします。

モジュール全体をテストするには v test 自分のモジュール とします。

v test . で、現在のフォルダ (とそれ以下) にあるすべてのテストを実行できます。

v test-stats を渡すと、各 _test.v ファイル内の個別のテストについて詳しく見ることができます。

メモリ管理

(工事中) ガベージコレクションや参照カウントはありません。V はコンパイル時で解放できるようにします。以下はその例です。

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

fn draw_scene() {
    ...
    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 はこれらのために事前確保したバッファを使用します。

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

ORM

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

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

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

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

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

ドキュメントを書く

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

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

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

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

ドキュメントを生成するには、v doc モジュール/への/パス を実行します。

ツール

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')
}

発展的なトピック

V から C 関数を呼び出す

#flag -lsqlite3
#include "sqlite3.h"

struct C.sqlite3 
struct C.sqlite3_stmt 

fn C.sqlite3_column_int(C.sqlite_stmt, int) int 

fn main() {
    path := 'sqlite3_users.db' 
    db := &C.sqlite3{!} // `sqlite3* db = 0` を意味する暫定の記法
    C.sqlite3_open(path.cstr(), &db)
    query := 'select count(*) from users' 
    stmt := &C.sqlite3_stmt{!} 
    C.sqlite3_prepare_v2(db, query.cstr(), - 1, &stmt, 0)
    C.sqlite3_step(stmt) 
    nr_users := C.sqlite3_column_int(stmt, 0)
    C.sqlite3_finalize(stmt)
    println(nr_users) 
} 

#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=

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 プロジェクト

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

よくあるゼロ終端の C 文字列は string(cstring)string(cstring, len) で V 文字列に変換できます。

注意: string/1string/2cstring のコピーを作成しないので、string() を呼び出した後にそれを解放しないでください。C 文字列のコピーを作成する必要がある場合 (getenv/1 のような 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* である byteptr
  • C の char* である charptr
  • C の char** である &charptr

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

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

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

生成された C コードでの問題をデバッグするために、これらのフラグを渡すことができます。

  • -cg - 様々なデバッグ情報を含む未最適化の実行ファイルを生成します。
  • -keep_c - 生成された C ファイルをそのままにします。デバッガもこれを使用できます。
  • -show_c_cmd - プログラムのビルドに使用された C コマンドを出力します。

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

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

条件付きコンパイル

$if windows {
    println('Windows')
}
$if linux {
    println('Linux')
}
$if macos {
    println('macOS')
}

$if debug {
    println('debugging')
}

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

コンパイル時疑似変数

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 コンパイラはそれらをインライン化しようとします。大抵の場合パフォーマンスが改善しますが、実行形式のサイズは膨らみます。

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

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

codegen を介したリフレクション

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

// TODO: 未実装
fn decode<T>(data string) T {
    mut result := T{}
    for field in T.fields {
        if field.typ == 'string' {
            result.$field = get_string(data, field.name)
        } else if field.typ == 'int' {
            result.$field = get_int(data, field.name)
        }
    }
    return result
}

// 以下が生成される
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 よりとても読みづらいですしね。

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

  • オーバーロードできる演算子は +-*/ だけです。
  • 他の関数を演算子関数の中で呼び出すことはできません。
  • 演算子関数の引数は変更できません。
  • 引数は両方とも同じ型でなければなりません (V のすべて演算子と同様です)。

インラインアセンブリ

TODO: まだ実装されていません

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

C/C++ を V へ変換する

TODO: C から V への変換は V 0.3 から利用できます。C++ から V へは今年の後半以降になります。

V は C/C++ のコードを可読性のある V のコードへ変換します。まずは簡単なプログラム test.cpp を作成してみましょう。

test.cpp
#include <vector>
#include <string>
#include <iostream>

int main() {
    std::vector<std::string> s;
    s.push_back("V is ");
    s.push_back("awesome");
    std::cout << s.size() << std::endl;
    return 0;
}

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

test.v
fn main {
    mut s := []
    s << 'V is '
    s << 'awesome'
    println(s.len)
}

オンラインでの C/C++ から V への翻訳機はまもなく登場します。

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.ls() の代わりに ls() が使えます)。

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

rm('build/*')
// 以下と同じ  
for file in ls('build/') {
    rm(file)
}

mv('*.v', 'build/')
// 以下と同じ  
for file in ls('.') {
    if file.ends_with('.v') {
        mv(file, 'build/')
    }
}

これで、これを通常の V プログラムのようにコンパイルし、どこでもデプロイ & 実行できる実行形式が得られます。

v deploy.vsh && ./deploy

もしくは伝統的な bash スクリプトのようにそのまま実行します。

v run deploy.vsh

Unix 系プラットフォームでは、chmod +x で実行可能にすれば ./deploy.vsh でファイルを直接実行できます。

属性

Vには、関数や構造体の動作を変更する属性がいくつかあります。

属性は、関数/構造体の宣言の直前の [] 内で指定するとその定義にのみ適用されます。

// この関数を呼び出すと非推奨の警告が出ます
[deprecated]
fn old_function() {}

// この関数の呼び出しはインライン化されます。
[inline]
fn inlined_function() {}

// この構造体は参照 (`&Window`) としてしか使用できず、ヒープ上にのみ確保できます。
[ref_only]
struct Window {
}

// フラグが false になっていると、この関数とその呼び出しのコードを生成しなくなります。
// フラグを使うには、`v -d flag` のようにします
[if debug]
fn foo() { }

fn bar() {
   foo() // `-d debug` が渡されていないと呼び出されない
}

// C との相互運用向けのみですが,この構造体が C 内で `typedef struct` によって定義されていると V に伝えます。
[typedef] 
struct C.Foo { }

// WINAPI での関数を宣言します
[windows_stdcall]
fn C.WinFunction()

補遺

補遺 I: キーワード

V には 23 のキーワードがあります。

break 
const  
continue 
defer 
else 
enum 
fn
for
go
goto
if
import
in 
interface 
match 
module 
mut 
none
or 
pub 
return
struct
type

補遺 II: 演算子

記号 意味 オペランド
+ integers, floats, strings
- integers, floats
* integers, floats
/ integers, floats
% 剰余 integers
& ビット AND integers
| ビット OR integers
^ ビット XOR integers
<< 左シフト integer << unsigned integer
>> 右シフト integer >> unsigned integer

優先度

優先度 演算子
5 * / % << >> &
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

代入演算子

+=   -=   *=   /=   %=
&=   |=   ^=
>>=  <<= 
MikuroXina
C++、TypeScript、DTM、シナリオを書く臆病者です
https://mikuroxina.github.io/portfolio
approvers
高専生による(限界)開発コミュニティ、 限界開発鯖(Approvers)のOrgnizationです!
https://approvers.dev/
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした