语句与表达式

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 表达式

不同于 C 语言,V 没有三元操作符(如 x = c ? 1 : 2)。相反,它支持将 if 作为表达式使用,语法更清晰但稍显冗长。将三元操作转换为 V 语言时(假设 c 是布尔条件),可写为 x = if c { 1 } else { 2 }

示例:

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

if 表达式的每个分支可包含多条语句,最后一行值作为该分支的返回值:

n := arguments().len
x := if n > 2 {
    dump(arguments())
    42
} else {
    println('something else')
    100
}
dump(x)

If 解包

在任何可使用 or {} 的地方,都可以用 "if 解包"。当表达式非 none 且非错误时,将解包后的值绑定到变量。

m := {
    'foo': 'bar'
}

// 处理缺失键
if v := m['foo'] {
    println(v) // bar
} else {
    println('not found')
}

返回结果类型的函数:

fn res() !int {
    return 42
}

if v := res() {
    println(v)
}

变量赋值解包:

struct User {
    name string
}

arr := [User{'John'}]

// 解包并赋值变量
u_name := if v := arr[0] {
    v.name
} else {
    'Unnamed'
}
println(u_name) // John

类型检查与转换

通过 is!is 可检查 sum type 的当前类型。

if 形式:

struct Abc {
    val string
}

struct Xyz {
    foo string
}

type Alphabet = Abc | Xyz

x := Alphabet(Abc{'test'}) // sum 类型
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)
} else if x.bar is MyStruct2 {
    new_var := x.bar as MyStruct2  // 使用 as 手动类型转换
    println(new_var)
}
match x.bar {
    MyStruct {
        // x.bar 自动转换
        println(x.bar)
    }
    else {}
}

可变变量可能改变,因此强制转换不安全。开发人员可用 mut 关键字标记表达式以显式操作:

mut x := MySumType(MyStruct{123})
if mut x is MyStruct {
    println(x)  // 强制转换可变变量
}
// match 形式相同
match mut x {
    MyStruct {
        println(x)
    }
}

Match

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

match 语句是 if-else 序列的简写形式。当匹配到分支时,执行其代码块;else 分支在所有其他分支不匹配时执行。

match 语句可返回值:

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

作为 if-else if-else 替代方案:

match true {
    2 > 4 { println('if') }
    3 == 4 { println('else if') }
    2 == 2 { println('else if2') }  // 输出 'else if2'
    else { println('else') }
}

或作为 unless 替代方案:

match false {
    2 > 4 { println('if') }  // 输出 'if'
    3 == 4 { println('else if') }
    2 == 2 { println('else if2') }
    else { println('else') }
}

match 返回匹配分支的最终表达式值:

enum Color {
    red
    blue
    green
}

fn is_red_or_blue(c Color) bool {
    return match c {
        .red, .blue { true }  // 逗号用于测试多个值
        .green { false }
    }
}

枚举分支:

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

匹配 sumtype 变体:

此时 match 是穷举式,无需 else 分支:

struct Dog {}
struct Cat {}
struct Veasel {}
type Animal = Dog | Cat | Veasel
a := Animal(Veasel{})
match a {
    Dog { println('Bay') }
    Cat { println('Meow') }
    Veasel { println('Vrrrrr-eeee') }  // 参考: https://www.youtube.com/watch?v=qTJEDyj2N0Q
}

范围匹配:

如果值在分支范围内,则执行该分支。范围使用 ...(三圆点,包含末尾元素),而非 ..(二圆点)。

const start = 1
const end = 10

c := 2
num := match c {
    start...end { 1000 }  // 输出 1000
    else { 0 }
}
println(num)

常量可用于范围表达式。

match 表达式在 for 循环和 if 语句中不可用。

In 操作符

in 用于检查数组或 map 是否包含元素。反向操作用 !in

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

in 检查 map 是否包含键,而非值。

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用于数组、map 或数字范围。

数组遍历:

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 index, value in arr 获取索引和值。

如需修改数组元素,声明变量为可变:

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

单下划线 _ 忽略标识符。

自定义迭代器:

实现 next 方法返回 Option 的类型可用于 for 循环。

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

fn (mut iter SquareIterator) next() ?int {
    if iter.idx >= iter.arr.len {
        return none
    }
    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
}

Map 遍历:

m := {
    'one': 1
    'two': 2
}
for key, value in m {
    println('${key} -> ${value}')  // 输出: one -> 1, two -> 2
}

单下划线忽略键或值:

// 仅遍历键
for key, _ in m {
    println(key)  // 输出: one, two
}
// 仅遍历值
for _, value in m {
    println(value)  // 输出: 1, 2
}

范围遍历:

for i in 0 .. 5 {  // 输出: 01234
    print(i)
}

low..high 表示独占范围(包含 low 但不包含 high)。

此设计基于 Edsger W. Dijkstra 的 "Why Numbering Should Start at Zero" (EWD831),强调逻辑一致性和减少错误。

条件遍历:

mut sum := 0
mut i := 0
for i <= 100 {  // 类似 while 循环
    sum += i
    i++
}
println(sum)  // 5050

布尔条件为 false 时停止,无需括号但需花括号。

无限循环:

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

C 风格 for:

for i := 0; i < 10; i += 2 {  // i 自动可变
    if i == 6 {
        continue  // 跳过 6
    }
    println(i)  // 输出: 0, 2, 4, 8
}

比 while 更安全,避免忘记更新计数器。

带标签的 break 和 continue

默认控制最内层循环,用标签指定外层循环:

outer: for i := 4; true; i++ {
    println(i)  // 输出: 4, 5, 6, 7
    for {
        if i < 7 {
            continue outer
        } else {
            break outer
        }
    }
}

标签必须紧贴外层循环前。

Defer

defer 语句延迟执行代码块,直到外围函数返回。

import os

fn read_log() {
    mut ok := false
    mut f := os.open('log.txt') or { panic(err) }
    defer {
        f.close()  // 文件将在函数结束时关闭
    }
    // ...
    if !ok {
        return  // defer 在此执行
    }
    // ...  // defer 在此执行
}

函数返回值时,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 {
        return f.writeln('This is a log file')  // 先写入再关闭
    } else if s == .return_error {
        return error('file open: ${f.is_opened}')  // 关闭前报告错误
    }
    return 0
}

访问函数结果:

单返回值用 $res(),多返回值用 $res(idx)

// 单返回值
fn (mut app App) auth_middleware() bool {
    defer {
        if !$res() {  // 访问返回值
            app.response.status_code = 401
        }
    }
    return true
}

// 多返回值
fn (mut app App) auth_with_user_middleware() (bool, string) {
    defer {
        if !$res(0) {  // 访问第一个返回值
            app.response.status_code = 401
        } else {
            app.user = $res(1)  // 访问第二个返回值
        }
    }
    return true, 'TestUser'
}

Goto

V 允许用 goto 无条件跳转到标签,但需在相同函数内,并标记为 unsafe(可能跳过变量初始化或访问已释放内存)。

if x {
    // ...
    if y {
        unsafe {
            goto my_label  // 跳转风险操作
        }
    }
    // ...
}
my_label:

优先使用 for 循环和带标签的 break/continue,这些更安全。