是的,最近大家都在吐槽学不动了,26个字母已经快不够用了,最近IT 行业的媒体又报道了一个号称和 C 语言一样快的 V 语言,其标准库仅为 400 KB,官网的描述为:
- 和C语言一样快
- 与C语言交互操作且无需要任何开销
- 最小化的内存分配
- B无需 runtime 反射的内置序列化
- 无需任何依赖即可编译为原生二进制:简单网页服务仅为65 KB
V 语言是否能成功业界是存在巨大质疑的,因为它除了所标榜的难以置信的速度之外所规划的功能也是超级强大。其V1.0版预计于今年年底正式发布,虽然在 GitHub 上获得了广泛的关注,其自身功能还是不完善的,在以下的中文文档中就可以看还有很多 TODO。一门语言的成功其实需要很多因素,整个生态和配套的内容都起来方才有机会进入主流编程界。想想 Go 语言从09年发布,拥有 Google 的强大支持,今天虽然也风声水起,但距离超越 Java 还是任重道远(Go语言布道师许式伟2012年时说 Go 语言10年内可达到第一位,并暗示会远早于那个时间,但目前增长有些乏力,当然这不影响它作为一门优秀语言,时至今日综合而论老大还是 C 语言)。我从没有追随过一门语言的成长,V 语言很可能不会成功,但其愿景还是很好的,权且感受一下吧,万一成功了呢~~
安装
1 2 3 4 5 6 7 |
git clone https://github.com/vlang/v cd v/compiler make sudo ln -s ~/v/compiler/v /usr/local/bin/v # 也可直接将文件拷贝到/usr/local/bin目录下 v -v # 查看版本 v # 进入 V 语言命令行 |
简介
V是为构建可维护软件而设计的静态类型编译的编程语言。
它类似于Go语言,同时受到Oberon, Rust, Swift的影响。
V是一种非常简单的语言。阅读本文档仅花费半小时左右的时间,读完后你差不多就掌握了整个语言。
虽然简单,它却为开发者赋予了强大的力量。在其它语言中能做的事都能使用V语言实现。
Hello World
1 2 3 4 5 6 7 8 |
# vi hello_world.v fn main() { println('hello world') } # 运行 v run hello_world.v # 或 v hello_world.v && ./hello_world |
函数使用 fn 来进行声明。返回类型放在函数名后。本例中 main 函数不返回任何内容,因此省略了类型。
和C语言及其它相关语言一样,main 是程序主入口。
println是一个内置的函数。它将值打印到标准输出中。
在单文件程序中fn main()的声明可以省略。这在编写小的脚本程序或进行语言学习时都非常有用。为保持简洁,本教程中将省略fn main()的使用。
这表示hello world程序可以简化为:
1 |
println('hello world') |
注释
1 2 3 4 5 |
// 这是单行注释 /* 这是多行注释 /* 可进行嵌套 */ */ |
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# vi functions.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 # 运行 v run functions.v } |
再强调下,类型放在参数名的后面。
类似于Go和C语言,函数无法进行重载。这简化了代码并提升了可维护性和可读性。
函数可在声明前进行使用,add 和 sub 都在 main 之后进行声明,但仍可被 main 所调用。在V语言中所有声明都是如此,这消除了对头文件的要求以及对文件和声明顺序的考虑。
变量
1 2 3 4 5 6 7 8 9 10 11 12 |
# vi variable.v fn main(){ name := 'Bob' age := 20 large_number := i64(9999999999) println(name) println(age) println(large_number) } # v run variable.v |
注:以上使用了 main 函数,当前如果不加的话使用文件的方式运行会报错,以下都按照原文省略了相应的 fn main 的定义
更新:v0.1.8之后无需 main 函数即可运行
变量使用 := 进行声明和初始化。这是在 V 语言中声明变量的唯一方式。这表示变量总是会存在初始值。
变量的类型可由右侧的值进行推断。要强制为不同的类型,可使用类型转换:表达式 T(v)会将值 v 转换为类型 T。
不同于大多数其它语言。V仅允许在函数中定义变量。不允许使用全局(模块级别)变量。在 V 语言中没有全局状态。
1 2 3 4 |
mut age := 20 println(age) age = 21 println(age) |
使用变量值时使用 =。V 语言中,变量默认不可变。要让变量的值可修改,需要使用 mut 来进行声明。
试试将代码第二行中的 mut 删除后再运行程序。
请注意:= 和 =的区别
:= 用于声明和初始化,=用于分配值。
1 2 3 |
fn main() { age = 21 } |
这段代码不会编译,因为没有声明变量 age。V 语言中的所有变量都需要进行声明。
1 2 3 |
fn main() { age := 21 } |
这段代码也不会编译,因为未使用的变量会导致编译错误。
1 2 3 4 5 6 |
fn main() { a := 10 if true { a := 20 } } |
不同于大多数语言,V 语言不允许影子变量(variable shadowing)。声明一个父级域中已经使用过的变量会产生编译错误。
基本类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
bool string i8 i16 i32 i64 i128 (soon) u8 u16 u32 u64 u128 (soon) byte // u8的别名 int // i32的别名 rune // i32的别名, 代表Unicode代码点 f32 f64 byteptr voidptr |
注意不同于 C 和 Go 语言,V语言中 int 是始终为32位的整型。
字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
name := 'Bob' println('Hello, $name!') // `$` 用于字符串内插 println(name.len) bobby := name + 'by' // + 用于连接字符串 println(bobby) // ==> "Bobby" println(bobby.substr(1, 3)) // ==> "ob" // println(bobby[1:3]) // 这种语法很可能会取代 substr()方法 mut s := 'hello ' s += 'world' // `+=` 用于追加字符串 println(s) // "hello world" |
V 语言中,字符串是一个只读取字节数组。字符串数据使用UTF-8进行编码。
字符串是不可变的。这表示substring函数会非常有效:不会执行拷贝,也不要求额外的空间分配。
单引号和双引号都可用于代表字符串。为保持一致,vfmt 将双引号转为单引号,除非字符串中包含单引号字符。
插值语法非常简单。它还可以配合字段使用'age = $user.age'
。如果你需要更为复杂的表达式,使用 ${}
: 'can register = ${user.age > 13}'
。
V 语言中的所有运算符必须在两侧拥有相同类型的值。如果 age 为 int 类型下面这段代码不会进行编译:
1 |
println('age = ' + age) |
我们需要将age转为字符串:
1 |
println('age = ' + age.str()) |
或使用字符串内插(更推荐):
1 |
println('age = $age') |
数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
nums := [1, 2, 3] println(nums) println(nums[1]) // ==> "2" mut names := ['John'] names << 'Peter' names << 'Sam' // names << 10 <-- 这不会编译。`names`是字符串数组。 println(names.len) // ==> "3" println('Alex' in names) // ==> "false" // 我们还可以预分配一定量的元素。 nr_ids := 50 mut ids := [0 ; nr_ids] // 这会创建带有50个0的数组。 |
数组类型由第一个元素决定,[1, 2, 3]是整型数组([]int)。[‘a’, ‘b’]是字符串数组([]string)。
所有元素类型必须相同。[1, ‘a’]不会进行编译。
<<运算符将值添加到数组的最后。
.len 字段返回数组的长度。注意,这是一个只读字段,不能由用户进行修改。V 语言中导出的字段默认都是只读的。
如果数组中包含 val则val in arry 返回 true。
Maps
1 2 3 4 5 6 7 8 9 10 11 12 |
mut m := map[string]int // 当前只允许字符串键的map 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, } |
if
1 2 3 4 5 6 7 8 9 |
a := 10 b := 20 if a < b { println('$a < $b') } else if a > b { println('$a > $b') } else { println('$a == $b') } |
if语句非常明显,和大多数其它语言是一样的。
不同于其它类 C 语言,条件旁边没有括号,大括号则是必须要有的。
if 可用作一个表达式:
1 2 3 4 5 6 7 8 |
num := 777 s := if num % 2 == 0 { 'even' } else { 'odd' } println(s) // ==> "odd" |
in 运算符
in 允许检查数组中是否包含一个元素。
1 2 |
nums := [1, 2, 3] println(1 in nums) // ==> true |
编写更为清晰和压缩的布尔表达式时也会很有用:
1 2 3 4 5 6 7 |
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。
1 2 3 4 5 6 7 8 |
numbers := [1, 2, 3, 4, 5] for num in numbers { println(num) } names := ['Sam', 'Peter'] for i, name in names { println('$i) $name') // Output: 0) Sam } // 1) Peter |
for value in 循环用于遍历数组中的元素。如果要求用到索引,可以转而使用另一种形式 for index, value in。
注意该值是只读的。如果你需要在循环时修改数组,则需要使用索引:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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 时停止遍历。
再强调一次,条件周围没有括号,而后面大括号是必须的。
1 2 3 4 5 6 7 8 |
mut num := 0 for { num++ if num >= 10 { break } } println(num) // ==> "10" |
可以省略条件,这会导致一个无限循环。
1 2 3 |
for i := 0; i < 10; i++ { println(i) } |
最后,有传递的 C 语言形式的 for 循环。它比 while 形式更安全,因为使用它会很容易忘记更新计数器而陷入无限循环。
这里 i 无需使用 mut 进行声明,因为定义中它总是可变的。
Match
1 2 3 4 5 6 7 |
os := 'windows' print('V is running on ') match os { 'darwin' => println('macOS.') 'linux' => println('Linux.') else => println(os) } |
match语句是编写一系列if - else
语句的更简短形式。在发现匹配分支时,=>后的表达式会进行执行。其它else =>分支在其它分支无法匹配时会运行。
译者注:很明显新版中使用 match来代替 switch
switch
1 2 3 4 5 6 7 8 9 10 11 |
os := 'windows' print('V is running on ') switch os { case 'darwin': println('macOS.') case 'linux': println('Linux.') default: println(os) } // TODO: 替换为匹配表达式 |
switch语句是对编写一系列 if – else 语句的简写方式。它运行第一个值与条件相等的 case 语句。
不同于 C,每个代码块的最后不需要使用 break 语句。
结构体
1 2 3 4 5 6 7 8 9 10 |
struct Point { x int y int } p := Point{ x: 10 y: 20 } println(p.x) // 结构体字段通过点号进行访问 |
注:结构体如放在 main函数内定义会报错
结构体位于栈中。要将结构体放到堆中并获取指向它的指针,使用&前缀:
1 2 |
pointer := &Point{10, 10} // 对包含3个及以下字段的结构体的替代初始化语法 println(pointer.x) // 指针访问字段的语法相同 |
V 语法没有子类,但支持嵌套结构体:
1 2 3 4 5 6 7 8 9 10 11 |
// TODO: 这会在8月稍后实现 struct Button { Widget title string } button := new_button('Click me') button.set_pos(x, y) // 不包含我们需要做的嵌套 button.widget.set_pos(x,y) |
结构体可拥有默认值:
1 2 3 4 5 6 7 8 |
struct Foo { a int b int = 10 } foo := Foo{} assert foo.a == 0 assert foo.b == 10 |
访问修饰符
结构体字段是私有的且默认不可变(这让结构体也不可变)。它们的访问修饰符可通过 pub 和 mut 进行修改。总共有5个可选项:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct Foo { a int // 私有不可变(默认) mut: b int // 私有可变 c int // (可以列出具有相同访问修饰符的多个字段) pub: d int // 公有不可变(只读) pub mut: e int // 公有但仅在父模块中可变 pub mut mut: f int // 公有且在父模块内外均可变 } // (不推荐使用,这也是它比较啰嗦的原因) |
例如,下面是在 builtin 模块中定义的 string 类型:
1 2 3 4 5 |
struct string { str byteptr pub: len int } |
从定义中很容易看出 string 是不可变类型。
带有字符串数据的字节指针在 builtin 外完全不可访问。len 字段是公有的,但不可变。
1 2 3 4 5 |
fn main() { str := 'hello' len := str.len // OK str.len++ // 编译错误 } |
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 的接收器。规定不是使用 self 或 this这样的接收器名称,而是使用简短的推荐仅一个字母的名称。
纯函数
V 语言的函数默认是纯粹的,这表示它们的返回值仅由参数决定,运行不存在副作用。
这由全局变量的缺位并且所有的函数参数默认不可变来实现,甚至是传递的是引用。
但V 语言是不是一个纯粹会函数式语言。可以通过使用相同的关键字 mut 来修改函数参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct User { mut: is_registered bool } fn (u mut User) register() { u.is_registered = true } mut user := User{} println(user.is_registered) // ==> "false" user.register() println(user.is_registered) // ==> "true" |
本例中,接收器(仅为第一个参数)标记为可变,因此 register()可以改变 user 对象。对一非接收器参数也是如此:
1 2 3 4 5 6 7 8 9 |
fn multiply_by_2(arr mut []int) { for i := 0; i < arr.len; i++ { arr[i] *= 2 } } mut nums := [1, 2, 3] multiply_by_2(mut nums) println(nums) // ==> "[2, 4, 6]" |
注意你需要在调用该函数时在 nums 前添加 mut。这会清晰地表明所调用函数会修改该值。
推荐返回值而非修改参数。修改参数仅应在你应用的性能紧要的部分使用来减少内存占用和拷贝。
使用 user.register()或 user = register(user)来代替 register(mut user)。
V 语言让返回对象的修改版本变得很容易:
1 2 3 4 5 |
fn register(u User) User { return { u | is_registered: true } } user = register(user) |
常量
1 2 3 4 5 6 7 |
const ( PI = 3.14 World = '世界' ) println(PI) println(World) |
注:同样常量在 main 函数内定义会报错
常量使用 const 进行声明。它们只能在模块级别定义(函数以外)。
常量名首字母必须大写。这有助于与变量进行区分。
常量的值无法进行修改。
V 语言中的常量比大多数语言都更为灵活。你可以分配更为复杂的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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) ) println(Numbers) println(Red) println(Blue) |
V 语言中不允许使用全局变量,因此这会非常有用。
模块
V语言是一种非常模块化的语言。鼓励创建可复用模块并且也非常简单。新建模块只需创建一个带有模块名的目录并添加 .v 代码文件:
1 2 3 4 5 6 7 8 9 10 11 |
cd ~/code/modules mkdir mymodule vim mymodule/mymodule.v // mymodule.v module mymodule // 导出一个函数需要使用`pub` pub fn say_hi() { println('hello from mymodule!') } |
你可以在 mymodule/添加任意数量的 .v 文件。
使用 v -lib ~/code/modules/mymodule 来进行构建。
这样就好了,现在可以在代码使用进行使用了:
1 2 3 4 5 6 7 |
module main import mymodule fn main() { mymodule.say_hi() } |
注意你需要在每次调用外部函数时指定模块。这一开始可能看上去有些多余,但会让代码可读性更强且易于理解,因为一直很清楚调用了哪个模块中的哪个函数。尤其是在大型的代码库中。
模块名称应该简短,在10个字符以内。不允许循环导入。
你可以在任何地方创建模块。
所有的模块会被静态编译到单个可热行文件中。
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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) { println(s.speak()) } dog := Dog{} cat := Cat{} perform(dog) // ==> "woof" perform(cat) // ==> "meow" |
一种类型通过实现其方法来实现接口。没有显示的相关声明,不存在 implements 关键字。
枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 |
enum Color { red green blue } fn main() { mut color := Color.red // V语言知道color是一个Color。这里无需使用 `color = Color.green`。 color = .green println(color) // "1" TODO: print "green"? if color == .green { println("it's green") } } |
选项/结果类型和错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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 { // 选项类型必须由or代码块处理 return // or代码块必须以return, break或continue结束 } println(user.id) // ==> "10" println(user.name) // ==> 'Charles' |
V 语言将Option和Result合并为同一个类型,因此你无需决定使用哪个。
将普通函数升级为 Optional 函数所需的工作很少:你需要对返回类型添加一个?并在出现问题时返回错误。
如果你无需返回错误,可以简单的使用 return None。
这是在 V 语言中处理错误的主要方式。像 Go 语言一样它们还是值,但优势在于错误不能取消处理,并处理它们会简洁很多。
err
在or
代码块中定义,并设置为传递给error()
函数的字符串信息。如果返回err
则err
为空。
1 2 3 4 |
user := repo.find_user_by_id(7) or { println(err) // "User 7 not found" return } |
你可以添加错误:
1 2 |
resp := http.get(url)? println(resp.body) |
http.get 返回 ?http.Response。它通过?进行调用,因此给调用的函数或在 main 产生 panic 时添加错误。
基本上以上代码是下面的简写版本:
1 2 3 4 |
resp := http.get(url) or { panic(err) } println(resp.body) |
泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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)? |
为便于阅读,允许使用⟨⟩而非<>。vfmt会自动将<>替换为⟨⟩。
并发
并发模型与 Go 语言非常相近。并发运行 foo(),只需要调用 go foo()。现在,它会在一个新的系统线程中启动该函数。很快会实现 goroutine 和调度器。
解码 JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct User { name string age int foo Foo [skip] // 使用skip属性来跳过某些字段 } data := '{ "name": "Frodo", "age": 25 }' user := json.decode(User, data) or { eprintln('Failed to decode json') return } println(user.name) println(user.age) |
JSON现在非常流行,这也是内置了对JSON的支持的原因。
json.decode 函数的第一个参数是要解码为的类型。第二个参数是JSON字符串。
V语言为JSON编码和解码生成代码。不使用任何runtime反射。这会产生更好的性能。
测试
1 2 3 4 5 6 7 8 9 |
// hello.v fn hello() string { return 'Hello world' } // hello_test.v fn test_hello() { assert hello() == 'Hello world' } |
所有测试函数需要放在*_test.v 文件中并以 test_ 开头。使用 v hello_test.v 来运行测试。使用 v test mymodule 来测试整个模块。
内存管理
没有垃圾回收或引用计数。V在编译时清理掉所能清理的内容。例如:
1 2 3 4 5 6 7 8 9 10 11 |
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,因此它们在函数退出时进行清理。
事实上,前两个调用不会产生任何内存分配。这两个字符串很小,V语言会对它们使用预分配的缓冲区。
对于更复杂的用例索南达杰发使用手动内存管理。很快会对此进行修复。
V 语言会在运行时监测内存泄漏并进行报告。例如要清理数组,可使用 free()方法:
1 2 3 |
numbers := [0; 1000000] ... numbers.free() |
defer
1 2 3 4 5 6 7 8 9 10 11 |
fn read_log() { f := os.open('log.txt') defer { f.close() } ... if !ok { // defer语句会在这里调用,会关闭该文件 return } ... // defer语句会在这里调用,会关闭该文件 } |
vfmt
你无需担心格式化自己的代码或样式指南,vfmt会为你处理这事事务:
1 |
v fmt file.v |
推荐设置自己的编辑器,来让vfmt在每次运行时保存。
保持在推代码前运行vfmt。
writing_documentation
它的动作方式非常类似于Go语言。非常简单:无需为你的代码编写文档,vdoc会通过源代码生成文档。
每个函数/类型/常量的文档必须放在声明之前:
1 2 3 |
// clearall clears all bits in the array fn clearall() { } |
注释必须以定义的名称开始。
模块的概述必须是放在模块名前的第一条注释。
要生成文档,运行v doc path/to/module(TODO这个暂时未启用)。
高级课题
通过V语言调用 C 语言函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#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{} 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(res, 0) C.sqlite3_finalize(res) println(nr_users) } |
编译时 if
1 2 3 4 5 6 7 8 9 |
$if windows { println('Windows') } $if linux { println('Linux') } $if mac { println('macOS') } |
编译时 if 以$起始。现在它仅能用于监测操作系统。
通过codegen的反射
拥有内置的JSON支持非常棒,但 V 语言还允许你创建有效的序列化器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// TODO: planned in August 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 } // generates to: fn decode_User(data string) User { mut result := User{} result.name = get_string(data, 'name') result.age = get_int(data, 'age') return result } |
有限的运算符重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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 语言中的其它运算符相同)
将 C/C++转译为 V
TODO: 7月时C语言对V语言的转译会可用。C++对V语言的转译会在今年下半年可用。
TODO: V 0.3.中C语言对V语言的转译会可用。C++对V语言的转译会在今年下半年可用。
V 语言可以将你的C/C++代码转译为易于阅读的 V 语言代码。我们先来创建一个简单的程序 test.cpp:
1 2 3 4 5 6 7 8 9 10 11 |
#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:
1 2 3 4 5 6 |
fn main { mut s := [] s << 'V is ' s << 'awesome' println(s.len) } |
在线 C/C++对 V 语言的转译器即将上线。
什么时候应当转译 C语言代码,又是什么时候直接通过 V 语言调用 C 语言代码呢?
如果你有编写良好、完整测试的 C 语言代码,那么你当然可以保持通过 V 语言来调用 C 的代码。
将其转译为 V 有几大好处:
- 如果你想要开发基代码,现在同一种语言已拥有一切,比在那个 C 中开发会更为安全和容易。
- 跨平台编译已容易很多。你完全无需担忧。
- 也无需更多的构建标记和包含文件。
代码热重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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]属性。
目前不可在程序运行时修改类型。
更多示例,包括图形化应用,请参见github.com/vlang/v/tree/master/examples/hot_code_reloading。
跨平台编译
对项目进行跨平台编译只需运行:
1 2 3 |
v -os windows . # 或 v -os linux . |
(对于 macOS 的跨平台编译暂不可实现。)
如果你没有 C 依赖,只需这么做即可。这甚至在使用 ui模块或使用 gg 的图形应用时可成功编译 GUI 应用。
V语言中的跨平台 shell 脚本
(7月底上线)
V 语言可以用作 Bash的替代来编写部署脚本、构建脚本等等。
使用 V 语言来进行编写的优势是语言的简易性和可预测性以及其跨平台的支持。V 语言脚本可动作在类 Unix 系统及 Windows 系统上。
在程序的开头使用#v 指令。这会让os 模块中的所有函数全局可用(例如这样你可以使用 ls()来代替 os.ls())。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#v rm('build/*') // Same as: for file in ls('build/') { rm(file) } mv('*.v', 'build/') // Same as: for file in ls('.') { if file.ends_with('.v') { mv(file, 'build/') } } |
现在你既可以像普通的 V 语言程序一样编译并获取可部署的可执行文件,在其它地方运行:v deploy.v && ./deploy。
也可以仅仅像传统的 bash 脚本那样进行运行:v run deploy.v。
附录一:关键字
V 语言有22个关键字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
break const continue defer else enum fn for go goto if import in interface match module mut or pub return struct type |
附录二:运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
+ 和 整型、浮点型、字符串 - 差 整型、浮点型 * 乘积 整型、浮点型 / 商 整型、浮点型 % 求余 整型 & 按位与 整型 | 按位或 整型 ^ 按位异或 整型 << 左移 整型 << 无符号整型 >> 右移 整型 >> 无符号整型 优先级 运算符 5 * / % << >> & 4 + - | ^ 3 == != < <= > >= 2 && 1 || 赋值运算符 += -= *= /= %= &= |= ^= >>= <<= |
示例
git clone https://github.com/vlang/v.git
1 2 3 |
# 进入 examples 相应目录,如俄罗斯方块 cd examples/tetris/ v run tetris.v |
常见问题
1、’GLFW/glfw3.h’ file not found
1 |
brew install glfw3 |
9/25/2019 有一段时间没有关注 V 语言了,这段时间修改了一些 TODO,很多原定于8月完成的尚未进行更新,其热度(Star 数)也止步在11k 了
12/27/2019 GitHub star数已达14k,安装包已增到1.0MB(Linux),此前声称8月上线及年底上线的目前均未实现