天天看點

V 語言中文文檔V 語言文檔Advanced TopicsAppendices

V 語言文檔

簡介

V是設計用于建構可維護軟體的靜态類型的編譯程式語言。
它與Go相似,其設計也受到Oberon,Rust,Swift,Kotlin和Python的影響。
V是一種非常簡單的語言。閱讀本文檔将花費您大約半小時的時間,到最後,您将學到全部語言。
該語言促進以最少的抽象編寫簡單清晰的代碼。盡管很簡單,但是V給開發人員很大的力量。
您可以使用其他語言進行的任何操作,都可以使用V語言進行。
           

目錄

  • Hello world
  • 注釋
  • 函數
    • 多值傳回
    • 可變數量的參數
  • 通路修飾符
  • 變量
  • 類型
    • Strings
    • Numbers
    • Arrays
    • Maps
  • 子產品引入
  • 邏輯控制與表達
    • If
    • In operator
    • For loop
    • Match
    • Defer
  • 結構體
    • 嵌入式結構體
    • 預設字段值
    • 短結構文字文法
    • 通路修飾符
    • 結構體方法
  • println和其他内置函數
  • 函數2
    • 純函數(預設)
    • 可變參數
    • 匿名和高階函數
  • 參考
  • 子產品-modules
  • 常量
  • 類型2
    • Interfaces
    • Enums
    • Sum types
    • Option/Result types & error handling
  • 泛型
  • 并發
  • JSON解析
  • 單元測試
  • 記憶體管理
  • ORM
  • 文檔撰寫
  • 工具類
    • vfmt
    • Profiling
  • 進階
    • 記憶體不安全代碼
    • 從V調用C函數
    • 調試生成的C代碼
    • 條件編譯
    • 編譯時僞變量
    • 編譯時反射
    • 限制運算符重載
    • 内聯彙編
    • 翻譯C/ c++到V
    • 代碼熱重載
    • 交叉編譯
    • V中跨平台shell腳本
    • 屬性
  • 附件
    • 關鍵字
    • 運算符

Hello World

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

将該代碼段儲存到檔案

hello.v

中 . 然後執行:

v run hello.v

.

這是已經把V添加到了環境變量中

如果還沒有, 看這裡.

,則必須手動鍵入到V的路徑。

祝賀您——您剛剛編寫了您的第一個V程式,并執行了它!

您可以編譯一個程式而不執行

v hello.v

.

v help

檢視所有支援的指令。

在上面的示例中,可以看到用。傳回類型緊跟在函數名之後。在本例中不傳回任何内容,是以可以省略傳回類型。

在大多數語言一樣(如C, Go和Rust),入口函數

main

println

是少數内置函數之一。它将傳遞給它的值列印到标準輸出。

fn main()

在一個檔案程式中可以跳過聲明。這在編寫小程式、“腳本”或學習語言時很有用。為簡潔起見,本教程将跳過這些内容。

這意味着“hello world”程式可以簡單為

println('hello world')
           

Comments

注釋
// 這是一個單行注釋。

/* 這是一個多行注釋。
   /* 它可以嵌套。 */
*/
           

Functions

函數
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中一樣,函數不能重載。

這簡化了代碼,提高了可維護性和可讀性。

函數可以在聲明之前使用:

add

sub

是在

main

後聲明的, 但是仍然可以在

main

中調用他們.

對于V中的所有聲明都是這樣,這樣就不需要頭檔案,也不需要考慮檔案和聲明的順序。

Returning multiple values

多值傳回
fn foo() (int, int) {
    return 2, 3
}

a, b := foo()
println(a) // 2
println(b) // 3
c, _ := foo() // 忽略值使用 `_`
           

Variable number of arguments

可變數量的參數
fn sum(a ...int) int {
    mut total := 0
    for x in a {
        total += x
    }
    return total
}
println(sum())    // Output: 0
println(sum(1))   //         1
println(sum(2,3)) //         5
           

Symbol visibility

通路修飾符
pub fn public_function() {
}

fn private_function() {
}
           

函數預設是私有的(沒有導出)。要允許其他子產品使用它們,請提前聲明

pub

該方法也适用于常量和類型。

Variables

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

變量被聲明和初始化 使用

:=

. 這是在v中聲明變量的唯一方法,這意味着變量總是有一個初始值。

變量的類型是從右側的值推斷出來的。

要選擇不同的類型,請使用類型轉換:表達式

T(v)

将值

v

轉換為類型

T

與大多數其他語言不同,V隻允許在函數中定義變量。不允許使用全局(子產品級)變量。V中沒有全局狀态

(詳情請看 Pure functions by default ).

Mutable variables

可變變量
mut age := 20
println(age)
age = 21
println(age)
           

要更改變量的值,請使用

:=

在V中,變量預設是不可變的。為了能夠改變變量的值,你必須用

mut

聲明它。

從第一行删除mut後,嘗試編譯上面的程式。

Initialization vs assignment

初始化和指派

注意以下(重要的)差別

:=

and

=

:=

用于聲明和初始化,

=

用于指派.

fn main() {
    age = 21
}
           

這段代碼無法編譯,因為沒有聲明變量“age”。所有變量都需要在V中聲明。

fn main() {
    age := 21
}
           

Declaration errors

申明錯誤

在開發模式下,編譯器将警告您沒有使用該變量(您将得到一個“未使用的變量”警告)。

在生産模式下(通過将

-prod

标志傳遞給v -

v -prod foo.v

來啟用),它根本不會編譯(就像在Go中一樣)。

fn main() {
    a := 10
    if true {
        a := 20 // error: shadowed variable
    }
    // warning: unused variable `a`
}
           

不像大多數語言,變量隐藏是不允許的。使用已在父範圍中使用的名稱聲明變量将導緻編譯錯誤。

Types

類型

Primitive types

原始類型(基礎類型)

bool

string

i8    i16  int  i64      i128 (soon)
byte  u16  u32  u64      u128 (soon)

rune // 表示Unicode代碼點

f32 f64

any_int, any_float // 數字文字的内部中間類型

byteptr, voidptr, charptr, size_t // 這些主要用于C語言的互操作性

any // 類似于C的void*和Go的接口{}
           

這裡請注意,和 C and Go 不同,

int

總是32位整數。

V中的所有運算符必須在兩邊都有相同類型的值,這是一個例外. 如果一端的小的資料類型完全符合另一端的類型的資料範圍,

則可以自動提升它。這些是允許的可能性:

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

例如,一個

int

值可以自動提升為

f64

i64

但不能提升為

f32

u32

.

(對于大值,

f32

意味着丢失精度,而對于負值,

u32

意味着丢失符号).

Strings

字元串

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

.

也支援類似于C中的

printf()

的格式說明符。

f

g

x

等是可選的,并指定輸出格式。編譯器負責存儲大小,是以沒有

hd

llu

println('x = ${x:12.3f}')
println('${item:-20} ${n:20d}')
           

V中的所有運算符必須在兩邊都有相同類型的值。如果“age”不是一個字元串(例如,如果“age”是一個“int”),這段代碼将不會編譯:

println('age = ' + age)
           

我們必須将

age

轉換為

string

:

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

或使用串插補(首選):

println('age = $age')
           

要表示字元字面量,請使用 `

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

對于原始字元串,在前加“r”。原始字元串不被轉義:

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

Numbers

數字

a := 123
           

這将把123的值指派給‘a’。預設情況下,“a”的類型是“int”。

你也可以使用十六進制,二進制或八進制符号的整型文字:

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)
           

如果沒有顯式指定類型,預設情況下float文字的類型為“f64”。

Arrays

數組

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

println(nums.len) // "3"
nums = [] // 數組現在是空的
println(nums.len) // "0"

// 聲明一個int類型的空數組:
users := []int{}
           

數組的類型由第一個元素決定:

  • [1, 2, 3]

    是int數組 (

    []int

    ).
  • ['a', 'b']

    是字元串數組 (

    []string

    ).

如果V不能推斷出數組的類型,使用者可以為第一個元素顯式地指定它:’ [byte(16), 32, 64, 128] ‘。

數組是同構的(所有元素必須具有相同的類型)。這意味着像’ [1,‘a’] '這樣的代碼将不能編譯。

字段

.len

傳回數組的長度。注意,它是一個隻讀字段,使用者不能修改它。

在V.中導出的字段預設是隻讀的。參見通路修飾符。

Array operations

數組操作

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

// append array
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"
           

<<

是将值追加到數組末尾的運算符。它還可以附加整個數組。

如果數組中包含

val

,則

val in array

傳回true。看

in

operator.

Initializing array properties

初始化數組屬性

在初始化的時候,你可以指定數組的容量(

cap

),或者它的初始長度(

len

)和預設元素(

init

):

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

設定容量可以提高插入的性能,因為它減少了需要重新配置設定的次數:

mut numbers := []int{ cap: 1000 }
println(numbers.len) // 0
// 現在附加元素不會重新配置設定
for i in 0 .. 1000 {
    numbers << i
}
           

注意:上面的代碼使用了range ’ for '語句。

Array methods

數組方法

使用’ 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']
           

it

是一個内置變量,它指的是目前在filter/map方法中正在處理的元素。

Multidimensional Arrays

多元數組

數組可以有多個次元。

2d array example:

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

3d array example:

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]]]
           

Sorting arrays

排序數組

對各種數組進行排序是非常簡單和直覺的。在提供自定義排序條件時使用特殊變量“a”和“b”。

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 := [...]
users.sort(a.age < b.age)   // sort by User.age int field
users.sort(a.name > b.name) // reverse sort by User.name string field
           

Maps

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
}
           

Module imports

子產品引入

有關建立子產品的資訊,請參見Modules

Importing a module

導入一個子產品

子產品可以使用關鍵字

import

導入。

import os

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

當使用來自其他子產品的常量時,必須給子產品名稱加上字首。不過,您也可以直接從其他子產品導入函數和類型:

import os { input }
import crypto.sha256 { sum }
import time { Time }
           

Module import aliasing

子產品導入别名

任何導入的子產品名稱都可以使用“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 main() {
    my_time := MyTime{
        year: 2020,
        month: 12,
        day: 25
    }
    println(my_time.unix_time())
}
           

Statements & expressions

邏輯控制與表達

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 check

檢查

你可以用

if

match

來檢查和類型。

struct Abc {
    val string
}
struct Xyz {
    foo string
}
type Alphabet = Abc | Xyz

x := Alphabet(Abc{'test'}) // sum type
if x is Abc {
    //x是自動轉換為Abc,可以在這裡使用
    println(x)
}
           

如果你有一個struct字段需要檢查,也有一種方法來命名一個别名。

if x.bar is MyStruct as bar {
   `x.bar` 不能自動進行轉換,而是使用 `as bar` 建立一個MyStruct類型的變量
    println(bar)
}
           

In operator

In 操作符

’ in '允許檢查 array或map是否包含元素。

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 loop

for 循環

V隻有一個循環關鍵字“for”,有幾種形式。

Array

for

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 arr

形式用于周遊數組中的元素。

如果需要索引,可以使用另一種形式’ for index, value in arr '。

注意,該值是隻讀的。如果你需要修改數組,則循環時,你必須使用索引:

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

當辨別符隻有一個下劃線時,它會被忽略。

Map

for

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

鍵或值都可以通過使用一個下劃線作為辨別符來忽略。

m := {'one':1, 'two':2}

// iterate over keys
for key, _ in m {
    println(key)  // Output: one
}                 //         two

// iterate over values
for _, value in m {
    println(value)  // Output: 1
}                   //         2
           

Range

for

// Prints '01234'
for i in 0..5 {
    print(i)
}
           

low..high

指一個獨占範圍,它表示從

low

到但不包括

high

的所有值。

Condition

for

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

這種形式的循環類似于其他語言中的“while”循環。

當布爾值為false時,循環将停止疊代。同樣,該條件周圍沒有括号,括号始終是必需的。

Bare

for

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

可以省略該條件,進而導緻無限循環。

C

for

類似C的普通 for循環
for i := 0; i < 10; i += 2 {
    // Don't print 6
    if i == 6 {
        continue
    }
    println(i)
}
           

最後,還有傳統的C風格的for循環。

它比

while

表單更安全,因為後者很容易忘記更新計數器,進而陷入無限循環。這裡

i

不需要用

mut

聲明因為根據定義它總是可變的。

Match

譯者注釋:類似 switch case
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'}
}
           

比對表達式傳回每個分支的最終表達式。

enum Color {
    red
    blue
    green
}

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

match語句還可以使用簡寫對枚舉

enum

的分支文法。

當所有分支都是窮舉的時,不允許使用

else

分支。

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

您還可以使用範圍作為

match

模式。如果該值落在一個分支的範圍内,則将執行該分支。

注意,範圍使用’…(三個點)而不是“…””(兩個點)。

這是因為範圍包含最後一個元素,而不是排除(如’…的範圍)。使用“…'将抛出一個錯誤。

Defer

defer語句是周圍的函數代碼塊執行完并傳回後延遲執行的語句塊。

fn read_log() {
    f := os.open('log.txt')
    defer { f.close() }
    ...
    if !ok {
        // 在這裡調用defer語句,檔案将被關閉
        return
    }
    ...
    // 在這裡調用defer語句,檔案将被關閉
}
           

Structs

結構體
struct Point {
    x int
    y int
}

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

println(p.x) // 使用點通路結構字段

// 3個字段或更少的結構的可選文字文法
p = Point{10, 20}
assert p.x == 10
           

Heap structs

堆結構

在堆棧上配置設定結構。要在堆上配置設定結構并獲得對它的引用,請使用

&

字首:

p := &Point{10, 10}
// 引用具有相同的文法來通路字段
println(p.x)
           

p的類型是&Point。這是一個reference指向。引用類似于Go指針和c++引用。

Embedded structs

嵌入式結構

V不允許子類,但它支援嵌入式結構:

// TODO: 這将在稍後實作
struct Button {
    Widget
    title string
}

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

// 如果沒有嵌入,我們就不得不這樣做
button.widget.set_pos(x,y)
           

Default field values

預設字段值

struct Foo {
    n   int      // n is 0 by default
    s   string   // s is '' by default
    a   []int    // a is `[]int{}` by default
    pos int = -1 // custom default value
}
           

在建立結構時,預設情況下所有結構字段都歸零。

數組和映射字段被配置設定。還可以定義自定義預設值。

Short struct literal syntax

短的結構執行個體建立文法
mut p := Point{x: 10, y: 20}

// 如果結構名是已知的,可以省略它
p = {x: 30, y: 4}
assert p.y == 4
           

省略結構名稱也可以用于傳回結構執行個體或将結構執行個體作為函數參數傳遞。

Trailing struct literal arguments

尾随結構執行個體參數

V沒有預設函數參數或命名參數,因為後面的結構文字文法可以代替:

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)
// the height is unset, so it's the default value
assert button.height == 20
           

如你所見,結構名和大括号都可以省略,而不是:

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

這隻适用于最後一個參數采用結構的函數。

Access modifiers

預設情況下,結構字段是私有的和不可變的(使得結構也是不可變的)。

它們的通路修飾符可以用“pub”和“mut”來更改。總共有5種可能的選擇:

struct Foo {
    a int   // 私有不可變的 (default)
mut:
    b int   // 私有可變
    c int   // (你可以用相同的通路修飾符列出多個字段)
pub:
    d int   // 公開不可變的(readonly)
pub mut:
    e int   // public,但隻在父子產品中是可變的
__global:
    f int   // 父子產品内部和外部都是公共的和可變的
}           // (不推薦使用,這就是為什麼‘global’關鍵字以__開頭)
           

例如,下面是在’ builtin ‘子產品中定義的’ string '類型:

struct string {
    str byteptr
pub:
    len int
}
           

從這個定義很容易看出‘string’是一個不可變類型。

帶有字元串資料的位元組指針在’内置’外部根本不可通路。len字段是公共的,但不可變的:

fn main() {
    str := 'hello'
    len := str.len // OK
    str.len++      // Compilation error
}
           

這意味着在V中定義公共隻讀字段非常容易,不需要在getter /setter或屬性中定義。

Methods

方法
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”這樣的收信人名字,

而是使用一個簡短的,最好是一個字母長的名字。

Functions 2

Pure functions by default

純函數( 預設)

V函數在預設情況下是純函數,這意味着它們的傳回值隻是它們的參數的函數,而且它們的計算沒有副作用(除了I/O)。

這是通過缺少全局變量和所有函數參數預設不變來實作的,即使在傳遞references時也是如此。

然而,V不是一種純粹的函數式語言。

有一個編譯器标志來啟用全局變量(’——enable-globals '),但是這是針對底層應用程式的,比如核心和驅動程式。

Mutable arguments

可變參數

可以使用關鍵字“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() '可以更改使用者對象。對于非接收方參數也是這樣:

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

注意,在調用此函數時,必須在’ nums ‘之前添加’ mut '。這表明被調用的函數将修改該值。

最好使用傳回值,而不是修改參數。修改參數應該隻在應用程式的性能關鍵部分進行,以減少配置設定和複制。

由于這個原因,V不允許用基本類型(比如整數)修改參數。隻有更複雜的類型(如數組和映射)可以修改。

Use

user.register()

or

user = register(user)

instead of

register(mut user)

.

V使它很容易傳回一個修改版本的對象:

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

user = register(user)
           

Anonymous & high order functions

匿名和高階函數
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
    })
}
           

References

參照
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
}
           

Constants

常量

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

println(pi)
println(world)
           

常量用“const”聲明。它們隻能在子產品級别(在函數之外)定義。

常量值永遠不能改變。

常數比大多數語言更靈活。您可以配置設定更複雜的值:

struct Color {
        r int
        g int
        b int
}

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}
    // evaluate function call at compile-time
    blue = rgb(0, 0, 255)
)

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

不允許使用全局變量,是以這可能非常有用。

println('Top cities: $TOP_CITIES.filter(.usa)')
vs
println('Top cities: $top_cities.filter(.usa)')
           

println and other builtin functions

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 '方法:

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)
           

如果不想列印換行符,可以使用

print()

代替。

内置函數的數量很低少。其他内建函數有:

fn exit(exit_code int)
fn panic(message string)
fn print_backtrace()
           

Modules

V是一種非常子產品化的語言。鼓勵建立可重用子產品,而且非常簡單。要建立一個新子產品,請建立一個包含子產品名稱的目錄和帶有代碼的.v檔案:

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

// mymodule.v
module mymodule

// To export a function we have to use `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() {
    // your setup code here ...
}
           

init函數不能是公共的。它将被自動調用。

Types 2

Interfaces

接口
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 {
    if s is Dog { //使用' is '檢查接口的底層類型
        println('perform(dog)')
	println(s.breed) // “s”自動轉換為“Dog”(智能轉換)
    } else if s is Cat {
        println('perform(cat)')
    }
    return s.speak()
}

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

類型通過實作其方法來實作接口。

沒有顯式聲明的意圖,沒有“implements”關鍵字。

Enums

枚舉
enum Color {
    red green blue
}

mut color := Color.red
// V知道 `color` 是一個 `Color` 。不需要使用`color = Color.green`。
color = .green
println(color) // "green"

match color {
    .red { ... }
    .green { ... }
    .blue { ... }
}

           

枚舉比對必須是窮舉的,或者有一個“else”分支。這確定了如果添加了新的enum字段,它将在代碼中的所有地方得到處理。

Sum types

組合類型

sum類型執行個體可以儲存幾種不同類型的值。使用“type”

關鍵字聲明一個組合類型:

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

type World = Moon | Mars | Venus

sum := World(Moon{})
           

若要檢查sum類型執行個體是否包含某個類型,請使用“sum is Type”。

要将sum類型轉換為它的一個變體,可以使用“sum as Type”:

fn (m Mars) dust_storm() bool

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

Matching sum types

組合類型比對

您還可以使用“match”來确定變體:

fn open_parachutes(n int)

fn land(w World) {
    match w {
        Moon {} // no atmosphere
        Mars {
            // light atmosphere
            open_parachutes(3)
        }
        Venus {
            // heavy atmosphere
            open_parachutes(1)
        }
    }
}
           

“match”必須為每個變體都有一個模式,或者有一個“else”分支。

有兩種方法可以通路比對分支中的cast變量:

  • match 傳入的變量
  • 使用’ as '指定變量名
fn (m Moon) moon_walk()
fn (m Mars) shiver()
fn (v Venus) sweat()

fn pass_time(w World) {
    match w {
        // using the shadowed match variable, in this case `w` (smart cast)
        Moon { w.moon_walk() }
        Mars { w.shiver() }
        else {}
    }
    // using `as` to specify a name for each value
    match w as var {
        Mars  { var.shiver() }
        Venus { var.sweat() }
        else {
            // w is of type World
            assert w is Moon
        }
    }
}
           

注意:隻有當比對表達式是一個變量時,隐藏才有效。它不能用于結構字段、數組索引或映射鍵查找。

Option/Result types and error handling

可選類型

可選類型使用

?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 automatically wraps this into an option type
            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 types must be handled by `or` blocks
        return
    }
    println(user.id) // "10"
    println(user.name) // "Charles"
}
           

V将“Option”和“Result”組合成一種類型,是以您不需要決定使用哪一種。

将一個功能“更新”為可選功能所需的工作量極小;

你必須加上一個’ ?傳回類型,并在出錯時傳回一個錯誤。

如果您不需要傳回錯誤消息,您可以簡單地’ return none ‘(這是比’ return error("") '的一個更有效的等效物)。

這是v中錯誤處理的主要機制,它們仍然是值,就像在Go中,

但是這樣做的好處是錯誤是不能被處理的,而且處理它們也不會那麼冗長。

與其他語言不同,V不通過‘throw/try/catch’塊處理異常。

err

定義在

or

塊中,并設定為傳遞的字元串消息

error()

函數。如果傳回

none

err

為空。

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

Handling optionals

可選類型(錯誤)處理

有四種方法處理可選的。第一種方法是

繼續抛出錯誤:

import net.http

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

http.get

傳回

?http.Response

. 因為

?

然後錯誤将被傳播到f的調用者。當使用

?

後一個

函數調用産生一個可選的,封閉函數必須傳回

也是可選的。如果在

main()

中使用了錯誤傳播

函數它将“恐慌”,因為錯誤不能傳播

任何進一步的。

f的主體本質上是以下内容的濃縮版:

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

第二種方法是提早結束執行:

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

在這裡,您可以調用’ panic() ‘或’ exit() ‘,這将停止整個程式的執行,

或者使用控制流語句(’ return ', ’ break ', ’ continue '等)從目前塊中斷開。

注意,“break”和“continue”隻能在“for”循環中使用。

V沒有辦法強制“unwrap”一個可選選項(像其他語言一樣,例如Rust的

unwrap()

或 Swift’s

!

)。為此,可以使用’ or {panic(err)} '。

第三種方法是在

or

塊的末尾提供一個預設值。萬一出現錯誤,

該值将被指派,是以它必須與被處理的“選項”的内容具有相同的類型。

fn do_something(s string) ?string {
    if s == 'foo' { return 'foo' }
    return error('invalid string') // Could be `return none` as well
}

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

第四種方法是使用“if”展開:

if resp := http.get(url) {
    println(resp.text) // resp is a http.Response, not an optional
} else {
    println(err)
}
           

上圖中,

http.get

傳回一個

?http.Response

resp

隻在第一個範圍内

if

分支。

err

隻在

else

分支 。

Generics

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

Another example:

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

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

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

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

Concurrency

并發運作

V的并發模型與Go的非常相似。要并發運作’ foo() ‘,隻需

用’ go foo() '調用它。現在,它在一個新系統上啟動該功能

線程。很快将實作協同程式和排程程式。

import sync
import time

fn task(id, duration int, mut wg sync.WaitGroup) {
    println("task ${id} begin")
    time.sleep_ms(duration)
    println("task ${id} end")
    wg.done()
}

fn main() {
    mut wg := sync.new_waitgroup()
    wg.add(3)
    go task(1, 500, mut wg)
    go task(2, 900, mut wg)
    go task(3, 100, mut wg)
    wg.wait()
    println('done')
}

// Output: task 1 begin
//         task 2 begin
//         task 3 begin
//         task 3 end
//         task 1 end
//         task 2 end
//         done
           

與Go不同,V還沒有通道。然而,資料可以在協同程式之間交換

以及通過共享變量調用的線程。這個變量應該作為引用建立并傳遞給

協同程式為

mut

。底層的

struct

也應該包含一個

mutex

來鎖定并發通路:

import sync

struct St {
mut:
	x int // 共享資料
	mtx &sync.Mutex
}

fn (mut b St) g() {
	...
	b.mtx.m_lock()
	// read/modify/write b.x
	...
	b.mtx.unlock()
	...
}

fn caller() {
	mut a := &St{ // create as reference so it's on the heap
		x: 10
		mtx: sync.new_mutex()
	}
	go a.g()
	...
	a.mtx.m_lock()
	// read/modify/write a.x
	...
	a.mtx.unlock()
	...
}
           

Decoding JSON

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無處不在的特性,對它的支援被直接建構到V中。

decode函數有兩個參數:’ JSON.decode '函數的第一個參數是JSON值應該被解碼到的類型,第二個參數是包含JSON資料的字元串。

V生成JSON編碼和解碼的代碼。不使用運作時反射。性能會好得多。

Testing

單元測試

// hello.v
fn hello() string {
    return 'Hello world'
}

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

assert

關鍵字也可以在測試之外使用。

所有測試函數都必須放在名為’ test的檔案中。v ‘和測試函數名必須以’ test '開頭。

您還可以定義一個特殊的測試函數:’ testsuite_begin ‘,它将是

在’ _test中的所有其他測試函數之前運作 '*.v '檔案。

您還可以定義一個特殊的測試函數:’ testsuite_end ‘,它将是

在’ _test中運作所有其他測試函數。v '檔案。

執行’ v hello_test.v '來運作測試。

要測試整個子產品,做’ v test mymodule '。

你也可以做“v測試”來測試目前檔案夾(和子目錄)中的所有内容。

可以将’ -stats '傳遞給v test,以檢視每個_test中各個測試的詳細資訊。v檔案。

Memory management

内測管理

(Work in progress)

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 ',是以當

函數退出。

實際上,前兩個調用根本不會導緻任何配置設定。

這兩根弦很小,

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

對象關系映射

(這仍然處于alpha版本)

V有一個内置的ORM(對象關系映射),它支援SQLite,并将很快支援MySQL、Postgres、MS SQL和Oracle。

V的ORM提供了許多好處:

-一種文法為所有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 syntax can be used to build queries
// db.select returns an array
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')
}

// by adding `limit 1` we tell V that there will be only one object
customer := sql db { select from Customer where id == 1 limit 1 }
println('$customer.id - $customer.name')

// insert a new customer
new_customer := Customer{name: 'Bob', nr_orders: 10}
sql db { insert new_customer into Customer }
           

For more examples, see vlib/orm/orm_test.v.

Writing Documentation

文檔編寫

它的工作方式和Go非常相似。這很簡單:沒有必要

為你的代碼單獨編寫文檔,vdoc會從源代碼中的文檔字元串生成它。

每個函數/類型/常量的文檔必須放在聲明之前:

// clearall clears all bits in the array
fn clearall() {

}
           

注釋必須以定義的名稱開始。

子產品的概述必須放在子產品名稱後面的第一個注釋中。

使用vdoc生成文檔,例如“vdoc net.http”。

Tools

v fmt

您不需要擔心格式化代碼或設定樣式指南。

“v fmt”就解決了這個問題:

v fmt file.v
           

建議設定編輯器,以便在每次儲存時運作“v fmt -w”。

vfmt運作通常非常便宜(花費小于30ms)。

總是跑

v fmt -w file.v

在輸入代碼之前。

Profiling

資料收集

V對分析你的程式有很好的支援:’ V -profile profile profile.txt run file.v ’

這将生成一個profile.txt檔案,然後您可以對該檔案進行分析。

生成的profile.txt檔案将有4列的行:

a)一個函數被調用的次數

b)一個函數總共花費了多少時間(ms)

c)調用一個函數平均花費多少時間(在ns中)

d) v函數的名稱

你可以排序列3(每個函數的平均時間)使用:

sort -n -k3 profile.txt|tail

你也可以使用秒表來測量你的代碼的部分顯式:

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

Advanced Topics

進階主題

Memory-unsafe code

Memory-unsafe代碼

有時,為了提高效率,您可能希望編寫底層代碼

損壞記憶體或容易受到安全攻擊。V支援編寫這樣的代碼,

但不是預設的。

V要求有意标記任何潛在的記憶體不安全操作。

對它們進行标記還可以向任何閱讀代碼的人表明可能存在

如果有錯誤,就會違反記憶體安全。

潛在的記憶體不安全操作的例子是:

  • 指針算術
  • 指針索引
  • 從不相容類型轉換為指針
  • 調用某些C函數,例如。" free ", " strlen “和” strncmp "

要标記潛在的記憶體不安全操作,請将它們封裝在一個“不安全”塊中:

// 配置設定2個未初始化的位元組&傳回一個對它們的引用
mut p := unsafe { &byte(malloc(2)) }
p[0] = `h` // Error: pointer indexing is only allowed in `unsafe` blocks
unsafe {
    p[0] = `h`
    p[1] = `i`
}
p++ // Error: pointer arithmetic is only allowed in `unsafe` blocks
unsafe {
    p++ // OK
}
assert *p == `i`
           

最佳實踐是避免将記憶體安全的表達式放入一個

unsafe

塊中,

是以使用

unsafe

的原因就越清楚越好。一般來說任何代碼

你認為是記憶體安全不應該在一個

unsafe

塊,是以編譯器

可以驗證它。

如果您懷疑您的程式确實違反了記憶體安全,那麼您就有了一個良好的開端

找出原因:檢視

unsafe

塊(以及它們是如何互相作用的)

周圍的代碼)。

  • 注意:這項工作正在進行中。

Calling C functions from V

從V中調用C函數

#flag -lsqlite3
#include "sqlite3.h"

// See also the example from 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
// ... you can also just define the type of parameter & leave out the C. prefix
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, cb_arg voidptr, emsg &charptr) int
fn C.sqlite3_free(voidptr)

fn my_callback(arg voidptr, howmany int, cvalues &charptr, cnames &charptr) int {
    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) // this means `sqlite3* db = 0`
    C.sqlite3_open('users.db', &db) // passing a string literal to a C function call results in a C string, not a V string
    // C.sqlite3_open(db_path.str, &db) // you can also use `.str byteptr` field to convert a V string to a C char pointer
    query := 'select count(*) from users'
    stmt := &C.sqlite3_stmt(0)
    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)
}
           

#flag

在你的V檔案頂部添加

#flag

指令來提供C編譯标志,比如:

  • -I

    用于添加C包括檔案搜尋路徑
  • -l

    用于添加您想要連結的C庫名稱
  • -L

    用于添加C庫檔案的搜尋路徑
  • -D

    用于設定編譯時變量

可以為不同的目标使用不同的标志。目前支援“linux”、“darwin”、“freebsd”和“windows”标志。

NB: 每一面flag必須在一行(目前)

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

Including C code

引入C代碼

您還可以在V子產品中直接包含C代碼。例如,假設您的C代碼位于子產品檔案夾中名為“C”的檔案夾中。然後:

  • Put a v.mod file inside the toplevel folder of your module (if you

    created your module with

    v new

    you already have v.mod file). For

    example:

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"
           

NB: @VROOT will be replaced by V with the nearest parent folder, where there is a v.mod file.

Any .v file beside or below the folder where the v.mod file is, can use

#flag @VROOT/abc

to refer to this folder.

The @VROOT folder is also prepended to the module lookup path, so you can import other

modules under your @VROOT, by just naming them.

The instructions above will make V look for an compiled .o file in your module

folder/c/implementation.o

.

If V finds it, the .o file will get linked to the main executable, that used the module.

If it does not find it, V assumes that there is a

@VROOT/c/implementation.c

file,

and tries to compile it to a .o file, then will use that.

This allows you to have C code, that is contained in a V module, so that its distribution is easier.

You can see a complete minimal example for using C code in a V wrapper module here:

project_with_c_code.

You can use

-cflags

to pass custom flags to the backend C compiler. You can also use

-cc

to change the default C backend compiler.

For example:

-cc gcc-9 -cflags -fsanitize=thread

.

C types

Ordinary zero terminated C strings can be converted to V strings with

string(cstring)

or

string(cstring, len)

.

NB: Each

string(...)

function does NOT create a copy of the

cstring

, so you should NOT free it after calling

string()

. If you need to make a copy of the C string (some libc APIs like

getenv

pretty much require that, since they

return pointers to internal libc memory), you can use

cstring_to_vstring(cstring)

.

On Windows, C APIs often return so called

wide

strings (utf16 encoding).

These can be converted to V strings with

string_from_wide(&u16(cwidestring))

.

V has these types for easier interoperability with C:

  • voidptr

    for C’s

    void*

    ,
  • byteptr

    for C’s

    byte*

    and
  • charptr

    for C’s

    char*

    .
  • &charptr

    for C’s

    char**

To cast a

voidptr

to a V reference, use

user := &User(user_void_ptr)

.

voidptr

can also be dereferenced into a V struct through casting:

user := User(user_void_ptr)

.

socket.v has an example which calls C code from V .

Debugging generated C code

To debug issues in the generated C code, you can pass these flags:

  • -cg

    - produces a less optimized executable with more debug information in it.
  • -showcc

    - prints the C command that is used to build the program.

For the best debugging experience, you can pass all of them at the same time:

v -cg -showcc yourprogram.v

, then just run your debugger (gdb/lldb) or IDE on the produced executable

yourprogram

.

If you just want to inspect the generated C code, without further compilation, you can also use the

-o

flag (e.g.

-o file.c

). This will make V produce the

file.c

then stop.

If you want to see the generated C source code for just a single C function, for example

main

, you can use:

-printfn main -o file.c

.

To see a detailed list of all flags that V supports, use

v help

,

v help build

,

v help build-c

.

Conditional compilation

條件預編譯

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

$if debug {
    println('debugging')
}
           

如果希望在編譯時計算’ If ‘,則必須以’ $ ‘符号作為字首。現在它隻能用于檢測

作業系統或’ -debug '編譯選項。

Compile time pseudo variables

編譯時僞變量

V還允許你的代碼通路一組僞字元串變量,這些變量在編譯時被替換:

  • @FN

    => replaced with the name of the current V function
  • @MOD

    => replaced with the name of the current V module
  • @STRUCT

    => replaced with the name of the current V struct
  • @FILE

    => replaced with the path of the V source file
  • @LINE

    => replaced with the V line number where it appears (as a string).
  • @COLUMN

    => replaced with the column where it appears (as a string).
  • @VEXE

    => replaced with the path to the V compiler
  • @VHASH

    => replaced with the shortened commit hash of the V compiler (as a string).
  • @VMOD_FILE

    => replaced with the contents of the nearest v.mod file (as a string).

That allows you to do the following example, useful while debugging/logging/tracing your code:

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

Performance tuning

性能調優

在編譯代碼時,生成的C代碼通常足夠快

-prod

. 但有些情況下,你可能會想要給予

編譯器的附加提示,以便進一步優化一些

代碼塊。

注:這些是很少需要,不應該使用,除非你

分析你的代碼,然後看到它們有顯著的好處。

引用gcc的文檔:“程式員在預測方面是出了名的糟糕

他們的程式是如何運作的。

[inline]

- 你可以用’ [inline] '來标記函數,是以C編譯器會這樣做

嘗試内聯它們,在某些情況下,這可能有利于性能,

但是可能會影響可執行檔案的大小。

[direct_array_access]

-在标有’ [direct_array_access] '的函數中

編譯器将數組操作直接轉換為C數組操作

嘔吐邊界檢查。這可以在疊代函數中節省大量時間

以使函數不安全為代價——除非

邊界将由使用者檢查。

if _likely_(bool expression) {

這提示C編譯器,即傳遞

布爾表達式很可能為真,是以可以生成程式集

代碼,有更少的機會的分支錯誤預測。在JS背景,

什麼也不做。

if _unlikely_(bool expression) {

類似于’ likely(x) ',但它暗示

布爾表達式是極不可能的。在JS後端,它什麼也不做。

Compile-time reflection

編譯時反射

擁有内置的JSON支援很好,但是V還允許您高效地建立

序列化器适用于任何資料格式。V有編譯時的“if”和“for”結構:

// TODO: not implemented yet

struct User {
    name string
    age  int
}

// Note: T should be passed a struct name only
fn decode<T>(data string) T {
    mut result := T{}
    // compile-time `for` loop
    // T.fields gives an array of a field metadata type
    $for field in T.fields {
        $if field.Type is string {
            // $(string_expr) produces an identifier
            result.$(field.name) = get_string(data, field.name)
        } else $if field.Type is int {
            result.$(field.name) = get_int(data, field.name)
        }
    }
    return result
}

// `decode<User>` generates:
fn decode_User(data string) User {
    mut result := User{}
    result.name = get_string(data, 'name')
    result.age = get_int(data, 'age')
    return result
}
           

Limited operator overloading

有限的操作符重載
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中的所有操作符一樣).

Inline assembly

内聯彙編

TODO: 沒有實作的

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

Translating C/C++ to V

TODO: 在v0.3中可以将C翻譯成V。c++到V将在今年晚些時候推出。

V可以把你的C/ c++代碼翻譯成人類可讀的V代碼。

讓我們建立一個簡單的程式“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;
}
           

Run

v translate test.cpp

and V will generate

test.v

:

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

一個線上的C/ c++到V轉換器即将面世。

什麼時候應該翻譯C代碼,什麼時候應該簡單地從V調用C代碼?

如果您有編寫良好、測試良好的C代碼,那麼當然您總是可以簡單地從V中調用此C代碼。

翻譯成V給你幾個好處:

-如果你計劃開發代碼庫,你現在所有的東西都用一種語言,這比用C更安全更容易開發。

交叉編譯變得容易多了。你根本不用擔心。

-沒有更多的建立标志和包括檔案。

Hot code reloading

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_reload) (https://github.com/vlang/v/tree/master/examples/hot_reload)。

Cross compilation

交叉編譯

要交叉編譯您的項目,隻需運作

v -os windows .
           

or

v -os linux .
           

(macOS的交叉編譯暫時不可能。)

如果您沒有任何C依賴項,那麼這就是您需要做的全部工作。這工作

當編譯GUI應用程式使用’ ui ‘子產品或圖形應用程式使用’ gg '。

您将需要安裝Clang, LLD連結器,并下載下傳一個zip檔案

庫和包含Windows和Linux的檔案。V将為您提供一個連結。

Cross-platform shell scripts in V

在V使用跨平台shell腳本

V可以作為Bash的替代品來編寫部署腳本、建構腳本等。

使用V進行此操作的優點是該語言的簡單性和可預測性

跨平台支援。“V腳本”可以在類unix系統和Windows上運作。

使用

.vsh

檔案擴充名。 它會使

os

中的所有函數

子產品全局化(例如,您可以使用

ls()

而不是

os.ls()

)。

#!/usr/local/bin/v run
//在類unix系統上,上面的shebang将檔案與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

Attributes

屬性

V有幾個屬性可以修改函數和結構體的行為。

屬性是在函數/結構聲明之前的

[]

中指定的,并且隻應用于下面的聲明。

// 調用此函數将導緻一個棄用警告
[deprecated]
fn old_function() {}

// 這個函數的調用将内聯。
[inline]
fn inlined_function() {}

// 下面的結構隻能用作引用(' &Window ')并在堆上配置設定。
[ref_only]
struct Window {
}

// 如果提供的标志為false, // V将不會生成此函數及其所有調用。
//要指明flag,請使用' v -d flag '
[if debug]
fn foo() { }

fn bar() {
   foo() // will not be called if `-d debug` is not passed
}

// 隻是用于C互操作,告訴V下面的結構是用C中的' typedef struct '定義的
[typedef]
struct C.Foo { }

// 在Win32 API代碼中使用時需要傳遞回調函數
[windows_stdcall]
fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)
           

Appendices

附件

Appendix I: Keywords

V有29個關鍵詞(3 are literals):

as
assert
break
const
continue
defer
else
enum
false
fn
for
go
goto
if
import
in
interface
is
match
module
mut
none
or
pub
return
struct
true
type
unsafe
           

參見Types.

Appendix II: Operators

附錄2: 運算符

這隻列出了原語類型的操作符。

+    sum                    integers, floats, strings
-    difference             integers, floats
*    product                integers, floats
/    quotient               integers, floats
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer


Precedence    Operator
    5             *  /  %  <<  >>  &
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||


Assignment Operators
+=   -=   *=   /=   %=
&=   |=   ^=
>>=  <<=
           

繼續閱讀