【在主画面加入捷径】
       
【选择语系】
繁中 简中

Rust 程序设计教学:变量 (Variable) 和数据类型 (Data Type)

【赞助商连结】

    在本章,我们介绍 Rust 程序的基本概念,包括 Rust 程序的组成、变量和类型。前几章的程序,大部分都很简单,但仍建议读者实际练习一次;即使只是看着书照着打一次,都会有一些些帮助,因为 (1) 藉由这个过程熟悉建立 Rust 程序的过程,(2) 对于一些初阶的错误,像是忘了加分号或打错函式名称,藉由实际练习才会改善。透过阅读得到知识的过程很快,但也很容易遗忘,透过肌肉操作得到知识的过程较慢,然而,一旦学会,记忆可维持较久。

    再访 Hello World

    我们重新检视 Hello World 范例:

    这个程序,分为两个部分,一个是 main函数 (function),一个是 println!("Hello, World"); 叙述 (statement)。函式是一种程序代码再利用的方式,将程序代码组织成函式,可重覆调用,避免撰写重覆的程序代码。main函数是一个特殊的函式,每个 Rust 应用程序都会有一个 main函数,而且也只能有一个 main函数。这个函式是程序的进入点。main函数固定的形式如下:

    现阶段,我们不需要在意函式的其他相关细节,只要记得程序代码要写在 main()函数中即可。

    println! 是一个宏 (macro),其作用为在终端机 (console) 印出字串及附加换行 (newline) 符号。在我们的范例中,println!("Hello, World") 就会印出 “Hello, World” 字串。宏是一种特殊的函式,在 Rust,宏会用惊叹号 ! 和一般的函式区别开来。现阶段,我们不需要了解宏的细节,就当成现有的函式即可。

    println!("Hello, World"); 是一条叙述。程序由许多叙述组成,每条叙述的最后面会加上分号 ;。在默认情形下,程序会由上往下,依序执行叙述。见以下范例:

    在本程序中,有两条叙述,而本程序会由上往下依序印出 “Hello, Rustacean.” 和 “Welcome to Rust Programming.” 这两行字串。

    注解

    在撰写程序时,注解 (comment) 是给程序设计者看的,不会影响程序的运行。Rust 有三种注解,分别为:

    • 单行注解 (line comments):以 // 开头,之后该行文字皆视为注解
    • 多行注解 (block comments):以一对 /* 和 */ 包起来,可跨越多行
    • 文件注解 (doc comments) 以 /// 开头,制作程序说明文件时使用

    以下为范例:

    虽然程序代码本身就足以说明程序运作的过程,良好的注解可使得阅读程序代码更有效率。本书会加入少量的注解以说明程序的运作。

    关键字

    每个程序语言都会有自己的关键字 (keyword),关键字在程序代码中被赋予特殊的意义,不能作为其他用途,像是 fn 表示定义一个函式。以下是 Rust 的关键字:

    读者不需要去背诵关键字,笔者在学习程序设计的过程中,也不会刻意去记忆关键字。因为:

    • 编辑器或 IDE 会用颜色提示使用者那些部分是关键字
    • 持续使用某个语言一段时间后,就会自然记住
    • 忘记时再查阅即可。像是每个程序语言的 else if 的写法都略有不同,过一阵子没用就忘了,也不需要刻意去记。

    变量

    使用变量

    在程序语言中,变量 (variable) 的作用在于储存数据 (data),在后续的程序代码中可再使用。可以把变量视为一个有标鑯的盒子,标签是变量名称,盒子内存值 (value)。见以下范例:

    在以上程序中,我们定义了变量 name,其值为字串 “Michael”。接着,我们将此变量传入 println!函数中印出。”Hello, {}” 的写法是字串安插 (string interpolation),会在后面接续变量或数据。let 是 Rust 的关键字,其作用为定义变量。

    我们再看另一个范例:

    在这个程序中,我们安插两个变量进入 println!函数中,结果会印出 “Goodbye, Michael” 字串。

    如果变量没有值,会发生什么事呢?见以下范例:

    这个程序会引发下列错误:

    这里隐含着两个概念,首先,变量要有相对应的值,否则会引发错误,再来,就是类型 (type) 的观念。程序语言的意义在于操作数据 (data),大部分的程序语言会将数据分类到不同的类型,Rust 也有许多的类型,我们后续会提到类型的相关概念。但是,即使我们加上类型的资讯,若没有指定值,仍然会引发错误。见以下范例:

    这时候,引发了另一个错误:

    这个错误告诉我们变量 x 未初始化,即未指定值。我们再改写一下程序:

    这次程序可正确地执行。虽然我们没有在程序中明确指定 x 的类型,Rust 可由 3 得知 x 为 i32 (32 位元整数) 类型。这是由于 Rust 提供类型推断 (type inference) 的功能,使得程序撰写起在像 Python 等高阶语言。

    变量名称

    目前 Rust 的变量名称采用以下守则:

    • 第一个字符为英文或底线 _
    • 第二个之后的字符为英文、数字或底线
    • 只有单一的底线 _ 不是变量

    以下是合 Rust 规范的变量名称:

    • x
    • x1
    • a_long_variable
    • aLongVariable
    • _var

    对于较长的变量名称,Rust 建议 snake case (像是 a_long_variable) 而非 camel case (像是 aLongVariable)。Rust 会对不符合其撰码风格的变量或函式名称发出警告讯息,但不会引发错误。虽然 Rust 支援 Unicode,但目前只能用英文字母来命名变量 (见 Rust issue #28979)。

    变量的可变性

    以下程序看似正常:

    但却引发了下列错误:

    Rust 和许多程序语言不同,在默认情形下,变量一旦赋值后就不能改变。然而,改变变量状态是程序设计常见的功能,要如何处理呢?Rust 要求程序设计者必需明确指出某个变量是可变的 (mutable)。使用较安全的规范,是 Rust 的特色,程序设计者要试着去适应 Rust 的思维。

    我们将程序改写如下:

    这次程序即可正确执行。我们在程序中使用 mut 这个关键字使 Rust 知道我们的变量 x 是可变的。

    常数

    Rust 另外提供 const 做为声明常数 (constant) 使用。constlet 的区别在于 const 的值要在编译期决定,仅能放入常值,不能放入函式回传值或其他变量值。通常常数用在程序中不会更动且在程序中会出现多次的值。

    数据类型

    我们在前面的程序中,使用了 Rust 的变量声明,却没有明确指定 Rust 的类型 (type)。Rust 就像大部分的程序语言,有许多的类型。在程序设计中,数据类型规范该数据在程序中允许的操作,像是数字可以加、减、乘、除,字串可以相接等。Rust 定义了数个基础类型 (primitive types),程序设计者可以直接对这些类型的数据进行 Rust 所定义的操作。除此之外,使用者也可以新增新的类型和其相关的操作。如果 Rust 可正确推断类型时,不需明确给定类型,但有时仍要明确给定类型,故仍然要有类型的概念。

    内建类型

    Rust 包括以下内建类型:

    • 布林 bool
    • 数字 (numbers)
      • 整数
      • 浮点数
    • 字符 char
    • 字串 (strings)
      • 字串 str
      • 字串物件
    • 数组 (array)、向量 (vector)、切片 (slice)
    • 元组 (tuple)
    • 指针 (pointer)
      • 参考 (reference)
      • Box
      • 裸指针 (raw pointers)

    布林

    Rust 内建布林 (boolean) 值,包括 truefalse 两种值。布林主要用于条件句 (condition),后续的章节会说明。

    字符

    字符代表单一的 Unicode scalar value (32 bit),字符以一对单引号 ' 括起来。

    字串

    Rust 中有两种字串类型,一种是 String 类,一种是 str。我们将于后续章节介绍字串的使用。

    数字

    Rust 有数种数字体别,主要可分为:

    • 整数

      • 有号固定整数:包括 i8i16i32i64
      • 无号固定整数:包括 u8u16u32u64
      • 变动整数:包括 isizeusize
    • 浮点数

      • 单精度 (六至九位精碓度):f32
      • 倍精度 (15 位精确度):f64

    有号和无号整数的差别在于是否有带正负号,这会影响该数字的最小值和最大值。例如,i8 的最小值为 -128,最大值为 127,而 u8 的最小值为 0,最大值为 255。固定整数有一定的位元数,而变动整数的位元数会因平台而有所不同。浮点数有两种,分别对应 IEEE-754 单精确度和双精确度浮点数。

    Rust 的整数有以下表示法:

    • 十进位数 (decimal):如 98_222
    • 十六进位数 (hex):如 0xff
    • 八进位数 (octal):如 0o77
    • 二进位数 (binary):如 0b1111_0000
    • Byte (仅限 u8):如 b'A'

    以下程序列出 Rust 的每个数字体别的最小值和最大值,读者可自行在自己的电脑上尝试。

    溢位 (overflow) 是程序在运算时,超过该类型的最大值;而下溢 (underflow) 则是程序在运算时,小于该数字体别的最小值。在 Rust 中,溢位或下溢会引发错误,这是较安全的设计。例如,以下程序引发溢位:

    显示以下错误讯息:

    由于电脑内部储存数字的方式,数字有位数的限制。如果要计算的数字较大,需使用大数运算相关套件透过软件仿真大数,如下例:

    若读者想实际执行本程序,需在 Cargo.toml 中加入外部套件,如下:

    数组、向量、切片、元组

    这些为容器 (collection),将于后续章节中介绍。

    指针

    Rust 的指针有以下数种:

    • 参考 (reference):受 Rust 管理的指针
    • Box:将数据存于内存的堆 (heap) 中
    • 裸指针 (raw pointers):相当于 C 或 C++ 的指针,需于 unsafe 区块才能使用

    我们将于后续文章中讨论指针。

    物件

    Rust 也支援面向对象程序,透过这种范式,程序设计者可以创造新的类型。不过,Rust 的物件系统和 Java 或 Python 等传统的物件系统略有不同,我们会于后续文章中讨论 Rust 的物件。

    【赞助商连结】