Sample Codes

一些示例代码

改变执行流的语法

if 分支

用来创建执行流的分支,像大多数其他语言一样。

规则是条件表达式不需要加小括号,而代码块必须加大括号。

要注意 CONDITION 必须是bool类型的表达式。

// "if" 表达式
if CONDITION {
    EXPRESSION
}

// "if-else" 表达式
if CONDITION {
    EXP1
} else {
    EXP2
}

// "if-else-if" 表达式
if COND1 {
    EXP1
} else if COND2 {
    EXP2
} else if COND3 {
    EXP3
} else if ...
... else {
    EXPN
}

再次强调,if 是一种表达式

// 三元表达式
if COND { EXP1 } else { EXP2 }

for 循环

各种循环都用 for 一个关键字就够了。

像其他语言一样,你当然可以在循环体里面用 breakcontinue 改变执行流。

While 型循环

我相信你知道这是什么。

要注意 COND 必须是一个bool类型的表达式

for COND {
    // do something
}

Loop 型循环

条件表达式总是 True 的While型循环,你也可以叫它死循环。

可以省略那个 true

for {
    // keep doing something
}

For 型循环

就是传统的 For 循环

for STATEMENT; CONDITION; STATEMENT {
    // do something
}

Do-While 型循环

对不起,没有设计这个

要用到的时候就这么凑合写吧:

for {
    // do something

    if COND { break; }
}

For-Each 型循环

这种循环是配合迭代器使用的

for let value : ITERATOR {
    print(value);
}

详情请查看专门讲迭代器的章节

表达式求值

既然 if 是表达式,为什么 for 不能也是呢?

Loop 型循环求值

先来看最简单的情况

value = for {
    // do something

    if COND { break EXPR; }
};
// value == EXPR

这是一个“无限循环”,仅当运行到 break 的时候结束循环。整个表达式的值就等于 break 时提供的值。

其他循环求值

这些循环不需要 break 也会自己结束,你需要给循环自己结束情况指定一个 Python 同款的 else 表达式,大括号是必要的。

value = for COND {
    // do something
} else {
    EXPR
};

COND 条件不成立、循环结束时,就会对 else 代码块求值,并将其作为 for 表达式的值了。

当然,有 else 并不会影响你用 break

value = for COND {
    // do something

    if COND { break EXPR1; }

    // do something
} else {
    EXPR2
};

match 匹配

一些语言里有 “switch 语句”,而 match 具有相同的功能,但是更加强大。

match EXPR {
    PATTERN1 => { EXPR1 }
    PATTERN2 => {
        EXPR2
    }
    PATTERN3 | PATTERN4 => {
        EXPR3
    }
    default => { EXPR4 }
}

太好了,不需要break,我们有救了!

以上代码完全等效于用 == 依次比较的实现:

let v = EXPR;
if v == PATTERN1 {
    EXPR1
} else if v == PATTERN2 {
    EXPR2
} else if v == PATTERN3 || v == PATTERN4 {
    EXPR3
} else {
    EXPR4
}

至于是否需要实现 Rust 语言那种更强的功能,以后再说

数据、变量、类型

定义变量到底是用let呢,varval呢,还是:=呢?好纠结! 算了,就用let吧。。

想必大家都知道什么是变量,在本语言中,用let关键字定义一个变量。

具体来说,首先先写一个let,再跟着变量的标识符,然后写=,然后是初始化表达式,最后别忘了分号;

let IDENTIFIER = INIT_EXPRESSION;

就是这样,很简单。

有些人喜欢说“把值绑定到了标识符上”,也行。

类型

编程的时候不同的数据有不同的类型,字符串是字符串,整数是整数,浮点数是浮点数。

无论是表达式还是变量,能够赋值的前提是二者的类型要兼容。 你不能把一个类型为整数的值赋给一个类型为字符串的变量,否则就会出问题。

定义变量的时候你可以在标识符后面加一个冒号:,再加一个类型名,指定这个变量的类型。 以下是一个正确的示例,其中i32是整数类型,string是字符串类型,稍后会介绍。

let my_number: i32 = 8693;
let my_string: string = "Tnze";

上下交换一下,就变成了一个错误的示例。具体来说,一个语法错误。 你不能把字符串当成整数,也不能把整数当作字符串。

let my_number: i32 = "Tnze";  // 错误
let my_string: string = 8693; // 错误

整数类型

整数的英语叫做 Integer,因此我们把整数类型表示为 i8i16i32i64i128i256i512i代表整数,后面紧跟着的数字代表计算机用多少个bit去表示它。

用的bit越多,能表示的范围越大,i8只能表示 -128 ~ 127 范围内的整数,而i32能表示的范围是 -2147483648 ~ 2147483647。

上面这些整数类型都有其对应的“无符号”版本,英文是 Unsigned Integer,用 u8u16u32u64u128u256u512 表示。

这里的“符号”指的就是负号,因此无符号整数全都是非负数。u8的表示范围是 0 ~ 255,而 u32 表示范围是 0 ~ 4294967295‌。

如果你看不懂上面在说什么,请用搜索引擎查找“计算机表示整数的方法”、“二进制”、“原码、反码和补码”

浮点数类型

所谓浮点数,就是有小数点的数,并且小数点还会“浮动”。

什么意思呢?就是说我们已经有了整数,那么怎么表示一个小数呢?

很简单,用两个整数,第一个整数告诉我们这个数是什么,第二个整数告诉我们小数点在哪里就可以了。

例如我要表达1.414这个小数,那么我只要让第一个数为1414,第二个数为1,不就可以表示“小数点在第一个数字后面”了吗。

欸,这不就是科学计数法么。。。

基于上述思想,业界就制定了一个叫做 IEEE 754 的标准,旨在用二进制的科学计数法来表示二进制的小数,也就是我们常说的浮点数。

与整数一样,浮点数也有不同bit数量的版本,我们分别表示为 f8f16f32f64

其中f32一般称作“单精度浮点数”,而f64一般称作“双精度浮点数”。 比较少用到的f16被称作“半精度浮点数”,f8被称作“四分之一精度浮点数”。

字符串类型

字符串类型名称是 string

它其实是一个 u8 的不定长数组,内部数据必须是有效的 UTF-8 编码字符串。

由于它是一个不定长数组,因此长度信息是储存在不定长数组结构里的,字符串结尾并不保证有一个\0结束标记。

结构体类型

struct {
    Foo: i32,
    Bar: string,
}

联合体类型

和C语言的联合体不同,本语言的联合体是一种 Tagged Union。 一个联合体类型的值内部有一个隐藏的成员,指示当前到底是哪一种变体。

union {
    Foo: i32,
    Bar: string,
}

数组类型

数组类型分两种,固定长度的与动态长度的。固定长度的数组叫做ary,而动态长度的数组叫做vec

固定长度的数组具有两个类型参数,元素类型T与数组长度Nary<T, N>

动态长度的数组只有一个类型参数:vec<T>

数组的创建

数组通过大括号初始化:

let my_array: ary<i32, 5> = ary<i32, 5>{1, 2, 3, 4, 5};
let my_vector: vec<u8> = vec<u8>{6, 7, 8, 9, 10, 11, 12};
数组的访问

通过方括号运算符访问数组元素,下标从0开始

let a = my_array[0];
let b = my_vector[1];
my_array[2] = 8192;
my_vector[3] = 0;
数组类型的转换

固定长度数组与动态长度数组可以互相转换。 当动态长度数组转换为静态长度数组时,如果长度不匹配则会出错

let foo = ary<i32, 3>{0, 0, 0};
let bar = vec<i32>(foo);
bar.push(1);
let foo2 = ary<i32, 4>(bar); // {0, 0, 0 1}

函数类型

详情请看专门讲函数的章节

运算

提供一组基本的运算符,可用于构建表达式。 运算符实际代表的运算与其应用在什么类型的数据上有关。

数字的加减乘除

包括 +, -, *, / 。可以应用在所有整数及浮点数类型上。

溢出时,结果取决于具体实现。

对于整数除法,还可以使用%求余数,像大多数语言一样。

数字的比较

可以用 >, <, >=, <= 比较数字大小。表达式的类型为bool

布尔值的运算

逻辑与:&&,逻辑或:||,逻辑非:!

函数:一等公民

我们把函数和闭包都叫做函数,类型也一样,就像 Go 语言的那样

函数字面量的一般形式如下

fn(param1: T1, param2: T2, ...) -> R {
    // do something
}

函数是一个值

你可以把它赋值给一个变量,赋值语句后面要记得加分号

let my_func = fn(param1: T1, param2: T2, ...) -> R {
    // do something
};

也可以作为一个函数的参数

func1(fn(param1: T1, param2: T2, ...) -> R {
    // do something
})

现在你就可以跟其他语言一样去调用它了

my_func(arg1, arg2);

函数可以递归调用

递归调用就是说在函数的内部调用函数本身

let fib = fn(n: u32) -> u32 {
    return if n == 0 {
        0
    } else if n == 1 {
        1
    } else {
        fib(n - 1) + fib(n - 2)
    };
};

这样的语法设计好像会对解释器造成困扰?因为确定 fib 的类型必须先解析后面的函数体, 而解析函数体的时候又要确认 fib 的类型。

所以应该先解析 fn(n: u32) -> u32 这个函数签名的部分,这样就确定了 fib 的类型。 再继续解析剩余的函数体,这样在调用 fib 的时候就没有问题了。大概。。。

能想出这样的代码,我怕不是个天才

let func = fn() {
    print("foo");
    func = fn() { print("bar"); };
};

func(); // 打印foo
func(); // 打印bar
func(); // 打印bar

函数可以捕获外部的值,此时你也可以叫它闭包

所以说闭包就是使用了外部变量的函数,明白了吗

let a = 10;

my_func = fn() {
    print(a); // 变量a不是在函数里面定义的,但是却可以使用
    a += 1;
};

my_func(); // 打印10
my_func(); // 打印11
a = 20;
my_func(); // 打印20

方法

方法是一种关联到特定类型上的函数