Rust 勸退系列 04:基本數據類型

大家好,我是站長 polarisxu。

這是 Rust 勸退系列的第 4 個教程,探討 Rust 中的基本數據類型,或叫標量類型(scalar type)。

Rust 和 Go 一樣,都是靜態類型語言,這表示每個變量的類型必須明確。和 Go 類似,大多數情況下,Rust 編譯器能夠推斷出某個值的類型,不需要我們顯示指定,寫起來有點類似於弱類型似語言。但有些情況下,必須明確告知編譯器我們使用什麼類型,在 Rust 中,這叫 「類型註解」(type annotations)。

對於類型註解,看一個常見的例子:

let guess = "42".parse().expect("Not a number!");

這是將字符串 "42" 轉爲數字 42。在 Go 語言中,一般這麼做:

guess, err := strconv.Atoi("42")
if err != nil {
  panic(err)
}

但上面的 Rust 代碼會報錯:

error[E0282]type annotations needed
 --> src/main.rs:2:9
  ||     let guess = "42a".parse().expect("Not a number!");
  |         ^^^^^^ consider giving `guess` a type

這和 Go 還是不太一樣。Go 中很多時候,數值類型會是 int。

爲了修復這個問題,我們需要爲 number 指定一個類型,比如 u32。

let guess: u32 = "42".parse().expect("Not a number!");

吐槽:在 Rust 中,類型註解和 Go 中一樣,放在變量後面。但 Rust 中變量和類型直接非得加一個冒號(:),而且一般冒號緊跟着變量名(rustfmt 的建議)。不知道冒號有啥特殊需要?!

Rust 內置如下基本數據類型:

01 整數類型

將整數類型整理爲一張表,如下:(用 Go 語言對應的類型作對比)

SGBsBx

吐槽:有時候 Rust 真的很節省,int、uint 直接省略爲 i、u,function 省略爲 fn。但有時候又很繁瑣(不簡潔),比如前面說到的變量和類型之間的冒號。。。

這裏用 u、i 的形式,也需要一段時間適應。。。

兩點說明:

在 Go 中,整型變量默認類型是 int,以下代碼可以證明這一點:

x := 32
fmt.Printf("%T\n", i)
// 輸出:int

那 Rust 中默認是什麼類型呢?

我想在 Rust 中找到一種辦法,打印變量類型,網上找到了這樣的辦法(有點挫):

// 打印變量類型的函數。該函數看不懂先放着。
fn print_type_of<T>(_: &T) {
  println!("{}", std::any::type_name::<T>())
}

fn main() {
    let x = 32;
    print_type_of(&x);
   // 輸出:i32
}

可見 Rust 中整型變量默認類型是 i32(即使在 64 位機器上,也是 i32)。這一定程度上說明,在 Go 中,整數一般建議用 int 類型;而 Rust 中,一般建議用 i32 類型。(所以,爲什麼開頭的 parse 不能默認推斷爲 i32 類型呢?怕溢出?)

更智能的類型推斷

上文說 Rust 和 Go 一樣,支持類型推斷。不過 Rust 的推斷更智能,怎麼個智能法?看下面的代碼:

// 打印變量類型的函數
fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

fn main() {
    let x = 32;
    let y: i8 = x;
    print_type_of(&x);
    print_type_of(&y)
}

根據上面的講解,x 應該是默認類型:i32。但實際上,x 和 y 的類型都是 i8。也就是說,因爲 x 的類型沒有顯示的指定(類型註解),Rust 編譯器會根據上下文(實際上是 let y: i8 = x 這句)推斷出 x 的類型應該和 y 一致,即 i8。

在 Go 中,int8 和 int 是不會進行隱式轉換的,Rust 也一樣,必須進行顯示轉換。但 Rust 的智能類型推斷,可以讓開發者少寫類型轉換的代碼。

比如上面代碼,在 Go 語言中是行不通的:

package main

import (
 "fmt"
)

func main() {
 x := 32
 var y int8 = x
 fmt.Printf("%T\n", x)
 fmt.Printf("%T\n", y)
}

會報錯:

cannot use x (type int) as type int8 in assignment

也就是說,Go 中的類型推斷不會考慮上下文,因此沒有 Rust 智能。

因爲編譯器的強大,VSCode 中(安裝 rust-analyzer)會有類型提示,這樣上面的 print_type_of 函數也不需要了。做了一個動圖,注意上面 x 的類型變化:

此外,isize 和 usize 類型一般用作某些集合的索引,以後文章會看到。

關於各種類型的表示範圍我列出了,因爲這個系列不是爲無編程經驗的人準備的。這個系列更多是爲 Go 愛好者準備的 Rust 教程,因此和 Go 一致的地方可能不會講。

02 浮點類型

和 Go 一樣,Rust 也有兩種浮點數類型:f32 和 f64,對應 Go 中的 float32 和 float64。和 Go 一樣,默認類型是 f64,可以通過類型註解指定具體的浮點類型。(採用 IEEE-754 標準表示,關於浮點數知識,可以閱讀我之前寫的文章。)

let x = 2.0; // 默認是 f64

一般地,整數類型和浮點類型都成爲數值類型。

數值類型有一些共同的東西。比如都支持基本的數學運算。此外,除了通過類型註解指定類型,數值類型還可以在字面值後面帶上類型後綴指定類型,比如:

let x = 2.0f32; // f32 類型
let y = 32i64; // i64 類型

03 布爾型

和 Go 語言一樣,Rust 中的布爾類型使用 bool 表示(咋沒用 b、bl 之類的縮寫呢?哈哈哈)。有兩個可能的值:true 和 false。

fn main() {
    let t = true;

    let f: bool = false; // 顯式指定類型註解
}

04 字符型

Rust 中的 char 表示字符類型,是 Rust 的基本類型,字面值由單引號指定。

let a = 'a';
let b = '中';
let c = '🤣';

可見,Rust 中的 char 類型和 Go 中的 rune 一樣,表示的是 Unicode 碼點,佔 4 個字節。

因爲 Rust 中的字符串很複雜,而且不是基本類型,因此留在以後講解。

05 小結

本文介紹了 Rust 中的四種基本數據類型:整型、浮點型、布爾型和字符型。其中,浮點型、布爾型和字符型分別對應 Go 中的浮點型、布爾型和 rune 類型,但整型,Go 和 Rust 有些許不一樣,上文已經詳細介紹了。此外,Go 中複數也是基本數據類型:complex64 和 complex128,而 Rust 中沒有,複數通過第三方庫實現,比如:https://crates.io/crates/easy_complex。

此外,你可能會說 Go 中還有一個基本類型:byte,而 Rust 沒有。其實 Go 中的 byte 只是 uint8 的別名。另外,string 在 Go 中是基本數據類型,而在 Rust 中不是。

本節內容就這些,你被勸退了嗎?歡迎留言交流!

我是 polarisxu,北大碩士畢業,曾在 360 等知名互聯網公司工作,10 多年技術研發與架構經驗!2012 年接觸 Go 語言並創建了 Go 語言中文網!著有《Go 語言編程之旅》、開源圖書《Go 語言標準庫》等。

堅持輸出技術(包括 Go、Rust 等技術)、職場心得和創業感悟!歡迎關注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/E5vsby9SMCHclMcONnAu9g