- Rust 教程
- Rust - 首頁
- Rust - 簡介
- Rust - 環境搭建
- Rust - HelloWorld 示例
- Rust - 資料型別
- Rust - 變數
- Rust - 常量
- Rust - 字串
- Rust - 運算子
- Rust - 決策制定
- Rust - 迴圈
- Rust - 函式
- Rust - 元組
- Rust - 陣列
- Rust - 所有權
- Rust - 借用
- Rust - 切片
- Rust - 結構體
- Rust - 列舉
- Rust - 模組
- Rust - 集合
- Rust - 錯誤處理
- Rust - 泛型
- Rust - 輸入輸出
- Rust - 檔案輸入/輸出
- Rust - 包管理器
- Rust - 迭代器和閉包
- Rust - 智慧指標
- Rust - 併發
- Rust 有用資源
- Rust 快速指南
- Rust - 有用資源
- Rust - 討論
Rust 快速指南
Rust - 簡介
Rust 是一種系統級程式語言,由 Graydon Hoare 開發。Mozilla Labs 後來收購了該專案。
應用程式語言與系統程式語言
像 Java/C# 這樣的應用程式語言用於構建軟體,這些軟體直接為使用者提供服務。它們幫助我們構建業務應用程式,如電子表格、文字處理器、Web 應用程式或移動應用程式。
像 C/C++ 這樣的系統程式語言用於構建軟體和軟體平臺。它們可以用於構建作業系統、遊戲引擎、編譯器等。這些程式語言需要高度的硬體互動。
系統和應用程式語言面臨兩個主要問題:
- 難以編寫安全的程式碼。
- 難以編寫多執行緒程式碼。
為什麼選擇 Rust?
Rust 關注三個目標:
- 安全
- 速度
- 併發
該語言旨在以簡單的方式開發高度可靠和快速的軟體。Rust 可用於編寫從高階程式到特定於硬體的程式。
效能
Rust 程式語言在設計上沒有垃圾回收器 (GC)。這提高了執行時的效能。
編譯時記憶體安全
使用 Rust 構建的軟體可以免受記憶體問題(如懸空指標、緩衝區溢位和記憶體洩漏)的影響。
多執行緒應用程式
Rust 的所有權和記憶體安全規則提供了無資料競爭的併發。
支援 WebAssembly (WASM)
WebAssembly 有助於在瀏覽器、嵌入式裝置或任何其他地方執行高計算密集型演算法。它以原生程式碼的速度執行。Rust 可以編譯成 WebAssembly 以實現快速、可靠的執行。
Rust - 環境搭建
Rust 的安裝透過 **rustup** 簡化了,rustup 是一個基於控制檯的工具,用於管理 Rust 版本和相關工具。
在 Windows 上安裝
讓我們學習如何在 Windows 上安裝 RUST。
在 Windows 上執行 Rust 程式,必須安裝 Visual Studio 2013 或更高版本,幷包含 C++ 工具。首先,從這裡下載 Visual Studio VS 2013 Express
下載並安裝適用於 Windows 的 **rustup** 工具。**rustup-init.exe** 可從這裡下載:Rust Lang
雙擊 **rustup-init.exe** 檔案。單擊後,將出現以下螢幕。
按 Enter 進行預設安裝。安裝完成後,將出現以下螢幕。
從安裝螢幕可以清楚地看到,Rust 相關檔案儲存在以下資料夾中:
C:\Users\{PC}\.cargo\bin
資料夾的內容為:
cargo-fmt.exe cargo.exe rls.exe rust-gdb.exe rust-lldb.exe rustc.exe // this is the compiler for rust rustdoc.exe rustfmt.exe rustup.exe
**Cargo** 是 Rust 的包管理器。要驗證是否已安裝 **cargo**,請執行以下命令:
C:\Users\Admin>cargo -V cargo 1.29.0 (524a578d7 2018-08-05)
Rust 的編譯器是 **rustc**。要驗證編譯器版本,請執行以下命令:
C:\Users\Admin>cargo -V cargo 1.29.0 (524a578d7 2018-08-05)
在 Linux/Mac 上安裝
要在 Linux 或 macOS 上安裝 **rustup**,請開啟終端並輸入以下命令。
$ curl https://sh.rustup.rs -sSf | sh
該命令下載一個指令碼並啟動 **rustup** 工具的安裝,該工具會安裝最新穩定版本的 Rust。系統可能會提示您輸入密碼。如果安裝成功,將顯示以下行:
Rust is installed now. Great!
安裝指令碼會在您下次登入後自動將 Rust 新增到系統 PATH 中。要立即開始使用 Rust 而不是重新啟動終端,請在 shell 中執行以下命令以手動將 Rust 新增到系統 PATH 中:
$ source $HOME/.cargo/env
或者,您可以將以下行新增到您的 ~/.bash_profile 中:
$ export PATH="$HOME/.cargo/bin:$PATH"
**注意** - 當您嘗試編譯 Rust 程式並收到指示連結器無法執行的錯誤時,表示您的系統上未安裝連結器,您需要手動安裝一個。
使用 Tutorials Point Coding Ground 進行 RUST 開發
讀取-評估-列印迴圈 (REPL) 是一種易於使用的互動式 shell,用於編譯和執行計算機程式。如果您想在瀏覽器中線上編譯和執行 Rust 程式,請使用 Tutorialspoint 的 Coding Ground。
Rust - HelloWorld 示例
本章透過 **HelloWorld** 示例解釋了 Rust 語言的基本語法。
建立 **HelloWorld-App** 資料夾,並在終端中導航到該資料夾
C:\Users\Admin>mkdir HelloWorld-App C:\Users\Admin>cd HelloWorld-App C:\Users\Admin\HelloWorld-App>
要建立 Rust 檔案,請執行以下命令:
C:\Users\Admin\HelloWorld-App>notepad Hello.rs
Rust 程式副檔名為 .rs。以上命令建立一個空檔案 **Hello.rs** 並在記事本中開啟它。將下面給出的程式碼新增到此檔案中:
fn
main(){
println!("Rust says Hello to TutorialsPoint !!");
}
以上程式定義了一個函式 main fn main()。fn 關鍵字用於定義函式。main() 是一個預定義函式,充當程式的入口點。println! 是 Rust 中的預定義宏。它用於將字串(此處為 Hello)列印到控制檯。宏呼叫始終用感嘆號 - ! 標記。
使用 **rustc** 編譯 **Hello.rs** 檔案。
C:\Users\Admin\HelloWorld-App>rustc Hello.rs
程式成功編譯後,會生成一個可執行檔案(file_name.exe)。要驗證是否生成了 .exe 檔案,請執行以下命令。
C:\Users\Admin\HelloWorld-App>dir //lists the files in folder Hello.exe Hello.pdb Hello.rs
- 執行 Hello.exe 檔案並驗證輸出。
什麼是宏?
Rust 提供了一個強大的宏系統,允許超程式設計。正如您在前面的示例中看到的,宏看起來像函式,只是它們的名稱以感嘆號(!)結尾,但它們不是生成函式呼叫,而是擴充套件成原始碼,這些原始碼與程式的其餘部分一起編譯。因此,與函式不同,它們為程式提供了更多執行時功能。宏是函式的擴充套件版本。
使用 println! 宏 - 語法
println!(); // prints just a newline
println!("hello ");//prints hello
println!("format {} arguments", "some"); //prints format some arguments
Rust 中的註釋
註釋是提高程式可讀性的方法。註釋可用於包含有關程式的其他資訊,例如程式碼作者、有關函式/構造的提示等。編譯器會忽略註釋。
Rust 支援以下型別的註釋:
單行註釋 ( // ) - // 和行尾之間的任何文字都被視為註釋
多行註釋 (/* */) - 這些註釋可以跨越多行。
示例
//this is single line comment /* This is a Multi-line comment */
線上執行
Rust 程式可以透過 Tutorialspoint 的 Coding Ground 線上執行。在編輯器選項卡中編寫 HelloWorld 程式,然後單擊執行以檢視結果。
Rust - 資料型別
型別系統表示語言支援的不同型別的值。型別系統會在程式儲存或操作提供的值之前檢查其有效性。這確保了程式碼按預期執行。型別系統還有助於更豐富的程式碼提示和自動文件。
Rust 是一種靜態型別語言。Rust 中的每個值都具有一定的資料型別。編譯器可以根據分配給變數的值自動推斷變數的資料型別。
宣告變數
使用 let 關鍵字宣告變數。
fn main() {
let company_string = "TutorialsPoint"; // string type
let rating_float = 4.5; // float type
let is_growing_boolean = true; // boolean type
let icon_char = '♥'; //unicode character type
println!("company name is:{}",company_string);
println!("company rating on 5 is:{}",rating_float);
println!("company is growing :{}",is_growing_boolean);
println!("company icon is:{}",icon_char);
}
在以上示例中,變數的資料型別將從分配給它們的值中推斷出來。例如,Rust 會將字串資料型別分配給變數 company_string,將浮點數資料型別分配給 rating_float 等。
println! 宏接受兩個引數:
- 特殊語法 { },它是佔位符
- 變數名或常量
佔位符將被變數的值替換
以上程式碼片段的輸出將是:
company name is: TutorialsPoint company rating on 5 is:4.5 company is growing: true company icon is: ♥
標量型別
標量型別表示單個值。例如,10、3.14、'c'。Rust 有四種主要的標量型別。
- 整數
- 浮點數
- 布林值
- 字元
我們將在後續章節中瞭解每種型別。
整數
整數是沒有小數部分的數字。簡單地說,整數資料型別用於表示整數。
整數可以進一步分類為有符號和無符號。有符號整數可以儲存負值和正值。無符號整數只能儲存正值。下面給出了整數型別的詳細描述:
| 序號 | 大小 | 有符號 | 無符號 |
|---|---|---|---|
| 1 | 8 位 | i8 | u8 |
| 2 | 16 位 | i16 | u16 |
| 3 | 32 位 | i32 | u32 |
| 4 | 64 位 | i64 | u64 |
| 5 | 128 位 | i128 | u128 |
| 6 | 體系結構 | isize | usize |
整數的大小可以是 體系結構。這意味著資料型別的大小將從機器的 體系結構 派生。大小為 體系結構 的整數在 x86 機器上為 32 位,在 x64 機器上為 64 位。體系結構整數主要用於索引某種集合。
說明
fn main() {
let result = 10; // i32 by default
let age:u32 = 20;
let sum:i32 = 5-15;
let mark:isize = 10;
let count:usize = 30;
println!("result value is {}",result);
println!("sum is {} and age is {}",sum,age);
println!("mark is {} and count is {}",mark,count);
}
輸出將如下所示:
result value is 10 sum is -10 and age is 20 mark is 10 and count is 30
如果將 age 的值替換為浮點數,以上程式碼將返回編譯錯誤。
整數範圍
每個有符號變體可以儲存從 -(2^(n-1) 到 2^(n-1) -1 的數字,其中 n 是變體使用的位數。例如,i8 可以儲存從 -(2^7) 到 2^7 -1 的數字 - 在這裡我們將 n 替換為 8。
每個無符號變體可以儲存從 0 到 (2^n)-1 的數字。例如,u8 可以儲存從 0 到 (2^8)-1 的數字,等於 0 到 255。
整數溢位
當分配給整數變數的值超過 Rust 為資料型別定義的範圍時,就會發生整數溢位。讓我們用一個例子來理解這一點:
fn main() {
let age:u8 = 255;
// 0 to 255 only allowed for u8
let weight:u8 = 256; //overflow value is 0
let height:u8 = 257; //overflow value is 1
let score:u8 = 258; //overflow value is 2
println!("age is {} ",age);
println!("weight is {}",weight);
println!("height is {}",height);
println!("score is {}",score);
}
無符號 u8 變數的有效範圍是 0 到 255。在上面的示例中,變數被賦予了大於 255 的值(Rust 中整數變數的上限)。執行時,上述程式碼將返回一個警告 - **警告 - 字面量超出 u8 範圍**,用於 weight、height 和 score 變數。超過 255 的溢位值將從 0、1、2 等開始。最終沒有警告的輸出如下所示:
age is 255 weight is 0 height is 1 score is 2
浮點數
Rust 中的浮點資料型別可以分為 **f32** 和 **f64**。f32 型別是單精度浮點數,f64 是雙精度浮點數。預設型別是 f64。請考慮以下示例以瞭解更多關於浮點資料型別的知識。
fn main() {
let result = 10.00; //f64 by default
let interest:f32 = 8.35;
let cost:f64 = 15000.600; //double precision
println!("result value is {}",result);
println!("interest is {}",interest);
println!("cost is {}",cost);
}
輸出將如下所示:
interest is 8.35 cost is 15000.6
自動型別轉換
Rust 中不允許自動型別轉換。請考慮以下程式碼片段。一個整數值被賦予浮點變數 **interest**。
fn main() {
let interest:f32 = 8; // integer assigned to float variable
println!("interest is {}",interest);
}
編譯器丟擲一個 **型別不匹配錯誤**,如下所示。
error[E0308]: mismatched types
--> main.rs:2:22
|
2 | let interest:f32=8;
| ^ expected f32, found integral variable
|
= note: expected type `f32`
found type `{integer}`
error: aborting due to previous error(s)
數字分隔符
為了方便閱讀大數字,我們可以使用視覺分隔符 _ 下劃線來分隔數字。即 50,000 可以寫成 50_000。這在下面的示例中顯示。
fn main() {
let float_with_separator = 11_000.555_001;
println!("float value {}",float_with_separator);
let int_with_separator = 50_000;
println!("int value {}",int_with_separator);
}
輸出如下所示:
float value 11000.555001 int value 50000
布林值
布林型別有兩個可能的值 - true 或 false。使用 **bool** 關鍵字宣告布林變數。
說明
fn main() {
let isfun:bool = true;
println!("Is Rust Programming Fun ? {}",isfun);
}
上述程式碼的輸出將是:
Is Rust Programming Fun ? true
字元
Rust 中的字元資料型別支援數字、字母、Unicode 和特殊字元。使用 **char** 關鍵字宣告字元資料型別的變數。Rust 的 char 型別表示 Unicode 標量值,這意味著它可以表示的不僅僅是 ASCII。Unicode 標量值的範圍從 **U+0000** 到 **U+D7FF** 和 **U+E000** 到 **U+10FFFF**(包括)。
讓我們考慮一個示例以瞭解更多關於字元資料型別的知識。
fn main() {
let special_character = '@'; //default
let alphabet:char = 'A';
let emoji:char = '😁';
println!("special character is {}",special_character);
println!("alphabet is {}",alphabet);
println!("emoji is {}",emoji);
}
上述程式碼的輸出將是:
special character is @ alphabet is A emoji is 😁
Rust - 變數
變數是程式可以操作的命名儲存。簡單來說,變數幫助程式儲存值。Rust 中的變數與特定資料型別相關聯。資料型別決定了變數記憶體的大小和佈局、可以在該記憶體中儲存的值的範圍以及可以對變數執行的操作集。
變數命名規則
在本節中,我們將學習變數命名的不同規則。
變數名可以由字母、數字和下劃線字元組成。
它必須以字母或下劃線開頭。
大小寫字母是不同的,因為 Rust 區分大小寫。
語法
在 Rust 中宣告變數時,資料型別是可選的。資料型別是從賦予變數的值中推斷出來的。
宣告變數的語法如下所示。
let variable_name = value; // no type specified let variable_name:dataType = value; //type specified
說明
fn main() {
let fees = 25_000;
let salary:f64 = 35_000.00;
println!("fees is {} and salary is {}",fees,salary);
}
上述程式碼的輸出將是 fees is 25000 and salary is 35000。
不可變
預設情況下,變數在 Rust 中是不可變的 - 只讀。換句話說,一旦將值繫結到變數名,就不能更改變數的值。
讓我們用一個例子來理解這一點。
fn main() {
let fees = 25_000;
println!("fees is {} ",fees);
fees = 35_000;
println!("fees changed is {}",fees);
}
輸出將如下所示:
error[E0384]: re-assignment of immutable variable `fees` --> main.rs:6:3 | 3 | let fees = 25_000; | ---- first assignment to `fees` ... 6 | fees=35_000; | ^^^^^^^^^^^ re-assignment of immutable variable error: aborting due to previous error(s)
錯誤訊息指示了錯誤的原因 - 您不能將值兩次賦予不可變變數 fees。這是 Rust 允許程式設計師編寫程式碼並利用其安全性與易併發性的眾多方式之一。
可變
變數預設情況下是不可變的。在變數名字首 **mut** 關鍵字以使其可變。可變變數的值可以更改。
宣告可變變數的語法如下所示:
let mut variable_name = value;
let mut variable_name:dataType = value;
Let us understand this with an example
fn main() {
let mut fees:i32 = 25_000;
println!("fees is {} ",fees);
fees = 35_000;
println!("fees changed is {}",fees);
}
程式碼片段的輸出如下所示:
fees is 25000 fees changed is 35000
Rust - 常量
常量表示不能更改的值。如果您聲明瞭一個常量,則其值絕不會更改。用於使用常量的關鍵字是 **const**。常量必須顯式型別化。以下是宣告常量的語法。
const VARIABLE_NAME:dataType = value;
Rust 常量命名約定
常量的命名約定與變數的命名約定類似。常量名稱中的所有字元通常都大寫。與宣告變數不同,**let** 關鍵字不用於宣告常量。
我們在下面的示例中使用了 Rust 中的常量:
fn main() {
const USER_LIMIT:i32 = 100; // Declare a integer constant
const PI:f32 = 3.14; //Declare a float constant
println!("user limit is {}",USER_LIMIT); //Display value of the constant
println!("pi value is {}",PI); //Display value of the constant
}
常量與變數
在本節中,我們將學習常量和變數之間的區別因素。
常量使用 **const** 關鍵字宣告,而變數使用 **let** 關鍵字宣告。
變數宣告可以選擇具有資料型別,而常量宣告必須指定資料型別。這意味著 const USER_LIMIT=100 將導致錯誤。
使用 **let** 關鍵字宣告的變數預設情況下是不可變的。但是,您可以選擇使用 **mut** 關鍵字對其進行修改。常量是不可變的。
常量只能設定為常量表達式,而不是設定為函式呼叫的結果或將在執行時計算的任何其他值。
常量可以在任何作用域中宣告,包括全域性作用域,這使得它們對於程式碼的許多部分需要了解的值很有用。
變數和常量的遮蔽
Rust 允許程式設計師宣告同名的變數。在這種情況下,新變數會覆蓋之前的變數。
讓我們用一個例子來理解這一點。
fn main() {
let salary = 100.00;
let salary = 1.50 ;
// reads first salary
println!("The value of salary is :{}",salary);
}
上述程式碼聲明瞭兩個名為 salary 的變數。第一個宣告被賦予 100.00,而第二個宣告被賦予 1.50。第二個變數在顯示輸出時會遮蔽或隱藏第一個變數。
輸出
The value of salary is :1.50
Rust 在遮蔽時支援不同資料型別的變數。
請考慮以下示例。
程式碼聲明瞭兩個名為 uname 的變數。第一個宣告被賦予字串值,而第二個宣告被賦予整數。len 函式返回字串值中的字元總數。
fn main() {
let uname = "Mohtashim";
let uname = uname.len();
println!("name changed to integer : {}",uname);
}
輸出
name changed to integer: 9
與變數不同,常量不能被遮蔽。如果上述程式中的變數被替換為常量,編譯器將丟擲一個錯誤。
fn main() {
const NAME:&str = "Mohtashim";
const NAME:usize = NAME.len();
//Error : `NAME` already defined
println!("name changed to integer : {}",NAME);
}
Rust - 字串
Rust 中的字串資料型別可以分為以下幾種:
字串字面量(&str)
字串物件(String)
字串字面量
字串字面量 (&str) 用於在編譯時已知字串值的情況。字串字面量是一組字元,硬編碼到變數中。例如,let company="Tutorials Point"。字串字面量位於模組 std::str 中。字串字面量也稱為字串切片。
以下示例聲明瞭兩個字串字面量 - company 和 location。
fn main() {
let company:&str="TutorialsPoint";
let location:&str = "Hyderabad";
println!("company is : {} location :{}",company,location);
}
字串字面量預設情況下是靜態的。這意味著字串字面量保證在整個程式的持續時間內有效。我們還可以像下面這樣顯式地將變數指定為靜態的:
fn main() {
let company:&'static str = "TutorialsPoint";
let location:&'static str = "Hyderabad";
println!("company is : {} location :{}",company,location);
}
上述程式將生成以下輸出:
company is : TutorialsPoint location :Hyderabad
字串物件
String 物件型別在標準庫中提供。與字串字面量不同,字串物件型別不是核心語言的一部分。它在標準庫中定義為公共結構 pub struct String。String 是一個可增長的集合。它是可變的且 UTF-8 編碼的型別。String 物件型別可用於表示在執行時提供的字串值。字串物件分配在堆中。
語法
要建立 String 物件,我們可以使用以下任何語法:
String::new()
上述語法建立一個空字串
String::from()
這將建立一個帶有某些預設值的字串,作為引數傳遞給 from() 方法。
以下示例說明了 String 物件的使用。
fn main(){
let empty_string = String::new();
println!("length is {}",empty_string.len());
let content_string = String::from("TutorialsPoint");
println!("length is {}",content_string.len());
}
上述示例建立了兩個字串 - 使用 new 方法建立的空字串物件和使用 from 方法從字串字面量建立的字串物件。
輸出如下所示:
length is 0 length is 14
常用方法 - 字串物件
| 序號 | 方法 | 簽名 | 描述 |
|---|---|---|---|
| 1 | new() | pub const fn new() → String | 建立一個新的空 String。 |
| 2 | to_string() | fn to_string(&self) → String | 將給定值轉換為 String。 |
| 3 | replace() | pub fn replace<'a, P>(&'a self, from: P, to: &str) → String | 用另一個字串替換模式的所有匹配項。 |
| 4 | as_str() | pub fn as_str(&self) → &str | 提取包含整個字串的字串切片。 |
| 5 | push() | pub fn push(&mut self, ch: char) | 將給定的 char 附加到此 String 的末尾。 |
| 6 | push_str() | pub fn push_str(&mut self, string: &str) | 將給定的字串切片附加到此 String 的末尾。 |
| 7 | len() | pub fn len(&self) → usize | 返回此 String 的長度(以位元組為單位)。 |
| 8 | trim() | pub fn trim(&self) → &str | 返回刪除了前導和尾隨空格的字串切片。 |
| 9 | split_whitespace() | pub fn split_whitespace(&self) → SplitWhitespace | 按空格拆分字串切片並返回迭代器。 |
| 10 | split() | pub fn split<'a, P>(&'a self, pat: P) → Split<'a, P> , where P is pattern can be &str, char, or a closure that determines the split. | 返回此字串切片子字串的迭代器,這些子字串由模式匹配的字元分隔。 |
| 11 | chars() | pub fn chars(&self) → Chars | 返回字串切片字元的迭代器。 |
圖示:new()
使用 new() 方法建立一個空字串物件,並將它的值設定為 hello。
fn main(){
let mut z = String::new();
z.push_str("hello");
println!("{}",z);
}
輸出
上述程式生成以下輸出:
hello
圖示:to_string()
要訪問 String 物件的所有方法,請使用 to_string() 函式將字串字面量轉換為物件型別。
fn main(){
let name1 = "Hello TutorialsPoint ,
Hello!".to_string();
println!("{}",name1);
}
輸出
上述程式生成以下輸出:
Hello TutorialsPoint , Hello!
圖示:replace()
replace() 函式接受兩個引數 - 第一個引數是要搜尋的字串模式,第二個引數是要替換的新值。在上面的示例中,Hello 在 name1 字串中出現了兩次。
replace 函式將字串 Hello 的所有出現替換為 Howdy。
fn main(){
let name1 = "Hello TutorialsPoint ,
Hello!".to_string(); //String object
let name2 = name1.replace("Hello","Howdy"); //find and replace
println!("{}",name2);
}
輸出
上述程式生成以下輸出:
Howdy TutorialsPoint , Howdy!
圖示:as_str()
as_str() 函式提取包含整個字串的字串切片。
fn main() {
let example_string = String::from("example_string");
print_literal(example_string.as_str());
}
fn print_literal(data:&str ){
println!("displaying string literal {}",data);
}
輸出
上述程式生成以下輸出:
displaying string literal example_string
圖示:push()
push() 函式將給定的 char 附加到此 String 的末尾。
fn main(){
let mut company = "Tutorial".to_string();
company.push('s');
println!("{}",company);
}
輸出
上述程式生成以下輸出:
Tutorials
圖示:push_str()
push_str() 函式將給定的字串切片附加到 String 的末尾。
fn main(){
let mut company = "Tutorials".to_string();
company.push_str(" Point");
println!("{}",company);
}
輸出
上述程式生成以下輸出:
Tutorials Point
圖示:len()
len() 函式返回字串中的字元總數(包括空格)。
fn main() {
let fullname = " Tutorials Point";
println!("length is {}",fullname.len());
}
輸出
上述程式生成以下輸出:
length is 20
圖示:trim()
trim() 函式刪除字串中前導和尾隨空格。注意,此函式不會刪除內聯空格。
fn main() {
let fullname = " Tutorials Point \r\n";
println!("Before trim ");
println!("length is {}",fullname.len());
println!();
println!("After trim ");
println!("length is {}",fullname.trim().len());
}
輸出
上述程式生成以下輸出:
Before trim length is 24 After trim length is 15
圖示:split_whitespace()
split_whitespace() 將輸入字串拆分為不同的字串。它返回一個迭代器,因此我們像下面這樣遍歷標記:
fn main(){
let msg = "Tutorials Point has good t
utorials".to_string();
let mut i = 1;
for token in msg.split_whitespace(){
println!("token {} {}",i,token);
i+=1;
}
}
輸出
token 1 Tutorials token 2 Point token 3 has token 4 good token 5 tutorials
圖示:split() 字串
split() 字串方法返回字串切片子字串的迭代器,這些子字串由模式匹配的字元分隔。split() 方法的限制是結果不能儲存以供以後使用。collect 方法可用於將 split() 返回的結果儲存為向量。
fn main() {
let fullname = "Kannan,Sudhakaran,Tutorialspoint";
for token in fullname.split(","){
println!("token is {}",token);
}
//store in a Vector
println!("\n");
let tokens:Vec<&str>= fullname.split(",").collect();
println!("firstName is {}",tokens[0]);
println!("lastname is {}",tokens[1]);
println!("company is {}",tokens[2]);
}
以上示例在遇到逗號(,)時分割字串fullname。
輸出
token is Kannan token is Sudhakaran token is Tutorialspoint firstName is Kannan lastname is Sudhakaran company is Tutorialspoint
示例:chars()
可以使用 chars 方法訪問字串中的單個字元。讓我們來看一個例子來理解這一點。
fn main(){
let n1 = "Tutorials".to_string();
for n in n1.chars(){
println!("{}",n);
}
}
輸出
T u t o r i a l s
使用 + 運算子連線字串
可以將一個字串值附加到另一個字串。這稱為連線或插值。字串連線的結果是一個新的字串物件。+ 運算子在內部使用 add 方法。add 函式的語法有兩個引數。第一個引數是 self – 字串物件本身,第二個引數是第二個字串物件的引用。如下所示:
//add function
add(self,&str)->String {
// returns a String object
}
示例:字串連線
fn main(){
let n1 = "Tutorials".to_string();
let n2 = "Point".to_string();
let n3 = n1 + &n2; // n2 reference is passed
println!("{}",n3);
}
輸出將如下所示
TutorialsPoint
示例:型別轉換
以下示例演示如何將數字轉換為字串物件:
fn main(){
let number = 2020;
let number_as_string = number.to_string();
// convert number to string
println!("{}",number_as_string);
println!("{}",number_as_string=="2020");
}
輸出將如下所示
2020 true
示例:Format! 宏
將兩個字串物件加在一起的另一種方法是使用名為 format 的宏函式。Format! 的用法如下所示。
fn main(){
let n1 = "Tutorials".to_string();
let n2 = "Point".to_string();
let n3 = format!("{} {}",n1,n2);
println!("{}",n3);
}
輸出將如下所示
Tutorials Point
Rust - 運算子
運算子定義將在資料上執行的一些函式。運算子作用於的資料稱為運算元。考慮以下表達式:
7 + 5 = 12
這裡,值 7、5 和 12 是運算元,而 + 和 = 是運算子。
Rust 中的主要運算子可以分類為:
- 算術運算子
- 位運算子
- 比較運算子
- 邏輯運算子
- 位運算子
- 條件運算子
算術運算子
假設變數 a 和 b 中的值分別為 10 和 5。
| 序號 | 運算子 | 描述 | 示例 |
|---|---|---|---|
| 1 | +(加法) | 返回運算元的和 | a+b 為 15 |
| 2 | -(減法) | 返回值的差 | a-b 為 5 |
| 3 | *(乘法) | 返回值的積 | a*b 為 50 |
| 4 | /(除法) | 執行除法運算並返回商 | a / b 為 2 |
| 5 | %(取模) | 執行除法運算並返回餘數 | a % b 為 0 |
注意:Rust 中不支援 ++ 和 -- 運算子。
關係運算符
關係運算符測試或定義兩個實體之間關係的型別。關係運算符用於比較兩個或多個值。關係運算符返回布林值:true 或 false。
假設 A 的值為 10,B 的值為 20。
| 序號 | 運算子 | 描述 | 示例 |
|---|---|---|---|
| 1 | > | 大於 | (A > B) 為 False |
| 2 | < | 小於 | (A < B) 為 True |
| 3 | >= | 大於或等於 | (A >= B) 為 False |
| 4 | <= | 小於或等於 | (A <= B) 為 True |
| 5 | == | 等於 | (A == B) 為 False |
| 6 | != | 不等於 | (A != B) 為 True |
邏輯運算子
邏輯運算子用於組合兩個或多個條件。邏輯運算子也返回布林值。假設變數 A 的值為 10,B 的值為 20。
| 序號 | 運算子 | 描述 | 示例 |
|---|---|---|---|
| 1 | && (與) | 只有當所有指定的表示式都返回 true 時,該運算子才返回 true | (A > 10 && B > 10) 為 False |
| 2 | ||(或) | 如果指定的表示式中至少有一個返回 true,則該運算子返回 true | (A > 10 || B >10) 為 True |
| 3 | ! (非) | 該運算子返回表示式的結果的反值。例如:!(>5) 返回 false | !(A >10 ) 為 True |
位運算子
假設變數 A = 2,B = 3。
| 序號 | 運算子 | 描述 | 示例 |
|---|---|---|---|
| 1 | & (按位與) | 它對整數引數的每個位執行布林 AND 運算。 | (A & B) 為 2 |
| 2 | | (按位或) | 它對整數引數的每個位執行布林 OR 運算。 | (A | B) 為 3 |
| 3 | ^ (按位異或) | 它對整數引數的每個位執行布林異或運算。異或意味著運算元一為真或運算元二為真,但不能兩者都為真。 | (A ^ B) 為 1 |
| 4 | ! (按位取反) | 它是一個一元運算子,透過反轉運算元中的所有位來操作。 | (!B) 為 -4 |
| 5 | << (左移) | 它將第一個運算元中的所有位向左移動第二個運算元中指定的位數。新位用零填充。將值左移一位相當於將其乘以 2,左移兩位相當於將其乘以 4,依此類推。 | (A << 1) 為 4 |
| 6 | >> (右移) | 二進位制右移運算子。左運算元的值向右移動右運算元指定的位數。 | (A >> 1) 為 1 |
| 7 | >>> (帶零的右移) | 此運算子與 >> 運算子類似,只是左移的位始終為零。 | (A >>> 1) 為 1 |
Rust - 決策制定
決策結構要求程式設計師指定一個或多個要由程式評估或測試的條件,以及如果條件確定為真則要執行的語句或語句,以及可選地,如果條件確定為假則要執行的其他語句。
下面是大多數程式語言中常見的決策結構的通用形式:
| 序號 | 語句和描述 |
|---|---|
| 1 | if 語句 if 語句由一個布林表示式後跟一個或多個語句組成。 |
| 2 | if...else 語句 if 語句後面可以跟一個可選的 else 語句,當布林表示式為假時執行該語句。 |
| 3 | else...if 和巢狀 if 語句 可以在另一個 if 或 else if 語句中使用一個 if 或 else if 語句。 |
| 4 | match 語句 match 語句允許將變數與值列表進行比較。 |
If 語句
if…else 結構在執行程式碼塊之前評估條件。
語法
if boolean_expression {
// statement(s) will execute if the boolean expression is true
}
如果布林表示式計算結果為真,則將執行 if 語句內的程式碼塊。如果布林表示式計算結果為假,則將執行 if 語句結束後的第一組程式碼(在右花括號之後)。
fn main(){
let num:i32 = 5;
if num > 0 {
println!("number is positive") ;
}
}
以上示例將列印number is positive,因為 if 塊指定的條件為真。
if else 語句
if 後面可以跟一個可選的else塊。如果 if 語句測試的布林表示式計算結果為假,則將執行 else 塊。
語法
if boolean_expression {
// statement(s) will execute if the boolean expression is true
} else {
// statement(s) will execute if the boolean expression is false
}
流程圖
if 塊保護條件表示式。如果布林表示式計算結果為真,則執行與 if 語句關聯的塊。
if 塊後面可以跟一個可選的 else 語句。如果表示式計算結果為假,則執行與 else 塊關聯的指令塊。
示例 - 簡單 if…else
fn main() {
let num = 12;
if num % 2==0 {
println!("Even");
} else {
println!("Odd");
}
}
以上示例列印變數中的值是偶數還是奇數。if 塊檢查值是否能被 2 整除以確定這一點。以下是以上程式碼的輸出:
Even
巢狀 If
else…if 梯用於測試多個條件。語法如下所示:
語法
if boolean_expression1 {
//statements if the expression1 evaluates to true
} else if boolean_expression2 {
//statements if the expression2 evaluates to true
} else {
//statements if both expression1 and expression2 result to false
}
使用 if…else…if 和 else 語句時,需要牢記以下幾點。
- if 可以有零個或一個 else,並且它必須位於任何 else..if 之後。
- if 可以有零個到多個 else..if,並且它們必須位於 else 之前。
- 一旦 else..if 成功,就不會測試任何剩餘的 else..if 或 else。
示例:else…if 梯
fn main() {
let num = 2 ;
if num > 0 {
println!("{} is positive",num);
} else if num < 0 {
println!("{} is negative",num);
} else {
println!("{} is neither positive nor negative",num) ;
}
}
程式碼段顯示該值是正數、負數還是零。
輸出
2 is positive
Match 語句
match 語句檢查當前值是否與值列表中的任何值匹配,這與 C 語言中的 switch 語句非常相似。首先,請注意 match 關鍵字後面的表示式不必用括號括起來。
語法如下所示。
let expressionResult = match variable_expression {
constant_expr1 => {
//statements;
},
constant_expr2 => {
//statements;
},
_ => {
//default
}
};
在以下示例中,state_code 與值列表MH, KL, KA, GA匹配 - 如果找到任何匹配項,則將字串值返回到變數state。如果未找到匹配項,則預設情況 _ 匹配並返回值Unkown。
fn main(){
let state_code = "MH";
let state = match state_code {
"MH" => {println!("Found match for MH"); "Maharashtra"},
"KL" => "Kerala",
"KA" => "Karnadaka",
"GA" => "Goa",
_ => "Unknown"
};
println!("State name is {}",state);
}
輸出
Found match for MH State name is Maharashtra
Rust - 迴圈
在某些情況下,可能需要重複執行一段程式碼。通常,程式設計指令是按順序執行的:函式中的第一個語句首先執行,然後是第二個語句,依此類推。
程式語言提供了各種控制結構,允許更復雜的執行路徑。
迴圈語句允許我們多次執行語句或語句組。下面是在大多數程式語言中迴圈語句的通用形式。
Rust 提供不同型別的迴圈來處理迴圈需求:
- while
- loop
- for
確定迴圈
迭代次數確定/固定的迴圈稱為確定迴圈。for 迴圈是確定迴圈的一種實現。
For 迴圈
for 迴圈執行程式碼塊指定的次數。它可用於迭代一組固定的值,例如陣列。for 迴圈的語法如下所示
語法
for temp_variable in lower_bound..upper_bound {
//statements
}
for 迴圈的示例如下所示
fn main(){
for x in 1..11{ // 11 is not inclusive
if x==5 {
continue;
}
println!("x is {}",x);
}
}
注意:變數 x 只能在 for 塊內訪問。
輸出
x is 1 x is 2 x is 3 x is 4 x is 6 x is 7 x is 8 x is 9 x is 10
不確定迴圈
當迴圈的迭代次數不確定或未知時,使用不確定迴圈。
不確定迴圈可以使用以下方法實現:
| 序號 | 名稱和描述 |
|---|---|
| 1 | While while 迴圈在每次指定的條件計算結果為真時執行指令 |
| 2 | Loop loop 是一個 while(true) 不確定迴圈 |
示例 - for while
fn main(){
let mut x = 0;
while x < 10{
x+=1;
println!("inside loop x value is {}",x);
}
println!("outside loop x value is {}",x);
}
輸出如下所示:
inside loop x value is 1 inside loop x value is 2 inside loop x value is 3 inside loop x value is 4 inside loop x value is 5 inside loop x value is 6 inside loop x value is 7 inside loop x value is 8 inside loop x value is 9 inside loop x value is 10 outside loop x value is 10
示例 - loop
fn main(){
//while true
let mut x = 0;
loop {
x+=1;
println!("x={}",x);
if x==15 {
break;
}
}
}
break 語句用於將控制權從構造中取出。在迴圈中使用 break 會導致程式退出迴圈。
輸出
x=1 x=2 x=3 x=4 x=5 x=6 x=7 x=8 x=9 x=10 x=11 x=12 x=13 x=14 x=15
Continue 語句
continue 語句跳過當前迭代中的後續語句,並將控制權返回到迴圈的開頭。與 break 語句不同,continue 不會退出迴圈。它終止當前迭代並開始後續迭代。
continue 語句的示例如下所示。
fn main() {
let mut count = 0;
for num in 0..21 {
if num % 2==0 {
continue;
}
count+=1;
}
println! (" The count of odd values between 0 and 20 is: {} ",count);
//outputs 10
}
以上示例顯示了 0 到 20 之間偶數值的數量。如果數字為偶數,則迴圈退出當前迭代。這是使用 continue 語句實現的。
0 到 20 之間奇數值的數量為 10
Rust - 函式
函式是可讀、可維護和可重用程式碼的構建塊。函式是一組用於執行特定任務的語句。函式將程式組織成邏輯程式碼塊。一旦定義,就可以呼叫函式來訪問程式碼。這使得程式碼可重用。此外,函式使程式程式碼易於閱讀和維護。
函式宣告告訴編譯器函式的名稱、返回型別和引數。函式定義提供了函式的實際主體。
| 序號 | 函式和描述 |
|---|---|
| 1 |
定義函式 函式定義指定如何完成特定任務。 |
| 2 |
呼叫或呼叫函式 必須呼叫函式才能執行它。 |
| 3 |
返回函式 函式還可以將值以及控制權一起返回給呼叫方。 |
| 4 |
引數化函式 引數是將值傳遞給函式的一種機制。 |
定義函式
函式定義指定如何完成特定任務。在使用函式之前,必須先定義它。函式體包含函式應執行的程式碼。命名函式的規則類似於變數。函式使用fn關鍵字定義。定義標準函式的語法如下所示
語法
fn function_name(param1,param2..paramN) {
// function body
}
函式宣告可以包含可選的引數。引數用於將值傳遞給函式。
示例 - 簡單函式定義
//Defining a function
fn fn_hello(){
println!("hello from function fn_hello ");
}
呼叫函式
必須呼叫函式才能執行它。此過程稱為**函式呼叫**。在呼叫函式時,應傳遞引數的值。呼叫另一個函式的函式稱為**呼叫函式**。
語法
function_name(val1,val2,valN)
示例:呼叫函式
fn main(){
//calling a function
fn_hello();
}
這裡,main()是呼叫函式。
說明
以下示例定義了一個函式fn_hello()。該函式將訊息列印到控制檯。main()函式呼叫fn_hello()函式。
fn main(){
//calling a function
fn_hello();
}
//Defining a function
fn fn_hello(){
println!("hello from function fn_hello ");
}
輸出
hello from function fn_hello
從函式返回值
函式還可以將值與控制一起返回給呼叫方。此類函式稱為返回函式。
語法
可以使用以下語法中的任何一種來定義具有返回型別的函式。
使用return語句
// Syntax1
function function_name() -> return_type {
//statements
return value;
}
沒有return語句的簡寫語法
//Syntax2
function function_name() -> return_type {
value //no semicolon means this value is returned
}
圖示
fn main(){
println!("pi value is {}",get_pi());
}
fn get_pi()->f64 {
22.0/7.0
}
輸出
pi value is 3.142857142857143
帶引數的函式
引數是將值傳遞給函式的一種機制。引數構成函式簽名的一部分。在呼叫函式期間,引數值將傳遞給函式。除非明確指定,否則傳遞給函式的值的數量必須與定義的引數數量匹配。
可以使用以下技術之一將引數傳遞給函式:
按值傳遞
當呼叫方法時,將為每個值引數建立一個新的儲存位置。實際引數的值將複製到其中。因此,在被呼叫方法內部對引數進行的更改不會影響引數。
以下示例聲明瞭一個變數no,它最初為5。該變數作為引數(按值)傳遞給mutate_no_to_zero()函式,該函式將值更改為零。在函式呼叫後,當控制權返回到main方法時,該值將保持不變。
fn main(){
let no:i32 = 5;
mutate_no_to_zero(no);
println!("The value of no is:{}",no);
}
fn mutate_no_to_zero(mut param_no: i32) {
param_no = param_no*0;
println!("param_no value is :{}",param_no);
}
輸出
param_no value is :0 The value of no is:5
按引用傳遞
當您按引用傳遞引數時,與值引數不同,不會為這些引數建立新的儲存位置。引用引數表示與提供給方法的實際引數相同的記憶體位置。可以透過在變數名前新增&來按引用傳遞引數值。
在下面給出的示例中,我們有一個變數no,它最初為5。將變數no的引用傳遞給mutate_no_to_zero()函式。該函式對原始變數進行操作。在函式呼叫後,當控制權返回到main方法時,原始變數的值將為零。
fn main() {
let mut no:i32 = 5;
mutate_no_to_zero(&mut no);
println!("The value of no is:{}",no);
}
fn mutate_no_to_zero(param_no:&mut i32){
*param_no = 0; //de reference
}
*運算子用於訪問變數param_no指向的記憶體位置中儲存的值。這也被稱為取消引用。
輸出將是:
The value of no is 0.
將字串傳遞給函式
main()函式將字串物件傳遞給display()函式。
fn main(){
let name:String = String::from("TutorialsPoint");
display(name);
//cannot access name after display
}
fn display(param_name:String){
println!("param_name value is :{}",param_name);
}
輸出
param_name value is :TutorialsPoint
Rust - 元組
元組是一種複合資料型別。標量型別只能儲存一種資料。例如,i32變數只能儲存單個整數值。在複合型別中,我們可以一次儲存多個值,並且它們可以是不同型別的值。
元組具有固定長度 - 一旦宣告,它們的大小就不能增長或縮小。元組索引從0開始。
語法
//Syntax1 let tuple_name:(data_type1,data_type2,data_type3) = (value1,value2,value3); //Syntax2 let tuple_name = (value1,value2,value3);
說明
以下示例顯示元組中的值。
fn main() {
let tuple:(i32,f64,u8) = (-325,4.9,22);
println!("{:?}",tuple);
}
println!("{ }",tuple)語法不能用於顯示元組中的值。這是因為元組是一種複合型別。使用println!("{:?}", tuple_name)語法列印元組中的值。
輸出
(-325, 4.9, 22)
說明
以下示例列印元組中的各個值。
fn main() {
let tuple:(i32,f64,u8) = (-325,4.9,22);
println!("integer is :{:?}",tuple.0);
println!("float is :{:?}",tuple.1);
println!("unsigned integer is :{:?}",tuple.2);
}
輸出
integer is :-325 float is :4.9 unsigned integer is :2
說明
以下示例將元組作為引數傳遞給函式。元組按值傳遞給函式。
fn main(){
let b:(i32,bool,f64) = (110,true,10.9);
print(b);
}
//pass the tuple as a parameter
fn print(x:(i32,bool,f64)){
println!("Inside print method");
println!("{:?}",x);
}
輸出
Inside print method (110, true, 10.9)
解構
解構賦值是Rust的一項功能,其中我們解包元組的值。這是透過將元組分配給不同的變數來實現的。
考慮以下示例:
fn main(){
let b:(i32,bool,f64) = (30,true,7.9);
print(b);
}
fn print(x:(i32,bool,f64)){
println!("Inside print method");
let (age,is_male,cgpa) = x; //assigns a tuple to
distinct variables
println!("Age is {} , isMale? {},cgpa is
{}",age,is_male,cgpa);
}
變數x是一個元組,它被分配給let語句。每個變數 - age、is_male和cgpa將包含元組中相應的值。
輸出
Inside print method Age is 30 , isMale? true,cgpa is 7.9
Rust - 陣列
在本章中,我們將學習陣列以及與其相關的各種特性。在我們學習陣列之前,讓我們看看陣列與變數有何不同。
變數具有以下限制:
變數本質上是標量的。換句話說,變數宣告一次只能包含一個值。這意味著要在一個程式中儲存n個值,需要n個變數宣告。因此,當需要儲存較大的值集合時,變數的使用不可行。
程式中的變數以隨機順序分配記憶體,從而難以按宣告順序檢索/讀取值。
陣列是值的同構集合。簡單來說,陣列是相同資料型別的值的集合。
陣列的特性
陣列的特性如下所示:
陣列宣告分配連續的記憶體塊。
陣列是靜態的。這意味著一旦初始化,陣列就不能調整大小。
每個記憶體塊代表一個數組元素。
陣列元素由一個唯一的整數標識,該整數稱為元素的下標/索引。
填充陣列元素稱為陣列初始化。
陣列元素的值可以更新或修改,但不能刪除。
宣告和初始化陣列
使用以下語法在Rust中宣告和初始化陣列。
語法
//Syntax1 let variable_name = [value1,value2,value3]; //Syntax2 let variable_name:[dataType;size] = [value1,value2,value3]; //Syntax3 let variable_name:[dataType;size] = [default_value_for_elements,size];
在第一個語法中,陣列的型別是在初始化期間從陣列第一個元素的資料型別推斷出來的。
圖示:簡單陣列
以下示例明確指定了陣列的大小和資料型別。println!()函式的{:?}語法用於列印陣列中的所有值。len()函式用於計算陣列的大小。
fn main(){
let arr:[i32;4] = [10,20,30,40];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
}
輸出
array is [10, 20, 30, 40] array size is :4
圖示:沒有資料型別的陣列
以下程式聲明瞭一個包含4個元素的陣列。在變數宣告期間沒有明確指定資料型別。在這種情況下,陣列將為整數型別。len()函式用於計算陣列的大小。
fn main(){
let arr = [10,20,30,40];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
}
輸出
array is [10, 20, 30, 40] array size is :4
圖示:預設值
以下示例建立一個數組並將其所有元素初始化為-1的預設值。
fn main() {
let arr:[i32;4] = [-1;4];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
}
輸出
array is [-1, -1, -1, -1] array size is :4
圖示:使用for迴圈的陣列
以下示例遍歷陣列並列印索引及其對應值。迴圈從索引0到4(最後一個數組元素的索引)檢索值。
fn main(){
let arr:[i32;4] = [10,20,30,40];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
for index in 0..4 {
println!("index is: {} & value is : {}",index,arr[index]);
}
}
輸出
array is [10, 20, 30, 40] array size is :4 index is: 0 & value is : 10 index is: 1 & value is : 20 index is: 2 & value is : 30 index is: 3 & value is : 40
圖示:使用iter()函式
iter()函式獲取陣列中所有元素的值。
fn main(){
let arr:[i32;4] = [10,20,30,40];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
for val in arr.iter(){
println!("value is :{}",val);
}
}
輸出
array is [10, 20, 30, 40] array size is :4 value is :10 value is :20 value is :30 value is :40
圖示:可變陣列
mut關鍵字可用於宣告可變陣列。以下示例聲明瞭一個可變陣列並修改了第二個陣列元素的值。
fn main(){
let mut arr:[i32;4] = [10,20,30,40];
arr[1] = 0;
println!("{:?}",arr);
}
輸出
[10, 0, 30, 40]
將陣列作為引數傳遞給函式
陣列可以按值或按引用傳遞給函式。
圖示:按值傳遞
fn main() {
let arr = [10,20,30];
update(arr);
print!("Inside main {:?}",arr);
}
fn update(mut arr:[i32;3]){
for i in 0..3 {
arr[i] = 0;
}
println!("Inside update {:?}",arr);
}
輸出
Inside update [0, 0, 0] Inside main [10, 20, 30]
圖示:按引用傳遞
fn main() {
let mut arr = [10,20,30];
update(&mut arr);
print!("Inside main {:?}",arr);
}
fn update(arr:&mut [i32;3]){
for i in 0..3 {
arr[i] = 0;
}
println!("Inside update {:?}",arr);
}
輸出
Inside update [0, 0, 0] Inside main [0, 0, 0]
陣列宣告和常量
讓我們考慮以下示例以瞭解陣列宣告和常量。
fn main() {
let N: usize = 20;
let arr = [0; N]; //Error: non-constant used with constant
print!("{}",arr[10])
}
編譯器將導致異常。這是因為陣列的長度必須在編譯時知道。這裡,變數“N”的值將在執行時確定。換句話說,變數不能用於定義陣列的大小。
但是,以下程式是有效的:
fn main() {
const N: usize = 20;
// pointer sized
let arr = [0; N];
print!("{}",arr[10])
}
以const關鍵字為字首的識別符號的值在編譯時定義,並且在執行時不能更改。usize是指標大小的,因此其實際大小取決於您編譯程式的體系結構。
Rust - 所有權
程式的記憶體可以分配在以下位置:
- 棧
- 堆
棧
棧遵循後進先出的順序。棧儲存大小在編譯時已知的資料值。例如,固定大小的i32變數是棧分配的候選者。它的大小在編譯時已知。所有標量型別都可以儲存在棧中,因為大小是固定的。
考慮字串的示例,該字串在執行時分配了一個值。此類字串的確切大小在編譯時無法確定。因此,它不是棧分配的候選者,而是堆分配的候選者。
堆
堆記憶體儲存大小在編譯時未知的資料值。它用於儲存動態資料。簡單來說,堆記憶體分配給在程式的生命週期中可能發生變化的資料值。與棧相比,堆是記憶體中組織性較差的區域。
什麼是所有權?
Rust中的每個值都具有一個稱為該值**所有者**的變數。Rust中儲存的每個資料都將有一個與其關聯的所有者。例如,在語法中:let age = 30,age是值30的所有者。
每個資料一次只能擁有一個所有者。
兩個變數不能指向同一記憶體位置。變數將始終指向不同的記憶體位置。
轉移所有權
可以透過以下方式轉移值的所有權:
將一個變數的值賦給另一個變數。
將值傳遞給函式。
從函式返回值。
將一個變數的值賦給另一個變數
Rust作為一門語言的主要賣點是其記憶體安全性。記憶體安全性是透過嚴格控制誰可以使用什麼以及何時限制來實現的。
考慮以下程式碼段:
fn main(){
let v = vec![1,2,3];
// vector v owns the object in heap
//only a single variable owns the heap memory at any given time
let v2 = v;
// here two variables owns heap value,
//two pointers to the same content is not allowed in rust
//Rust is very smart in terms of memory access ,so it detects a race condition
//as two variables point to same heap
println!("{:?}",v);
}
以上示例聲明瞭一個向量v。所有權的概念是隻有一個變數繫結到資源,要麼v繫結到資源,要麼v2繫結到資源。以上示例丟擲錯誤:use of moved value: `v`。這是因為資源的所有權已轉移到v2。這意味著所有權從v移動到v2(v2=v),並且v在移動後無效。
將值傳遞給函式
當我們將堆中的物件傳遞給閉包或函式時,值的所有權也會發生變化。
fn main(){
let v = vec![1,2,3]; // vector v owns the object in heap
let v2 = v; // moves ownership to v2
display(v2); // v2 is moved to display and v2 is invalidated
println!("In main {:?}",v2); //v2 is No longer usable here
}
fn display(v:Vec<i32>){
println!("inside display {:?}",v);
}
從函式返回值
傳遞給函式的所有權將在函式執行完成後失效。對此的一種解決方法是讓函式將擁有的物件返回給呼叫方。
fn main(){
let v = vec![1,2,3]; // vector v owns the object in heap
let v2 = v; // moves ownership to v2
let v2_return = display(v2);
println!("In main {:?}",v2_return);
}
fn display(v:Vec<i32>)->Vec<i32> {
// returning same vector
println!("inside display {:?}",v);
}
所有權和原始型別
對於原始型別,一個變數的內容被複制到另一個變數。因此,不會發生所有權移動。這是因為原始變數需要的資源少於物件。考慮以下示例:
fn main(){
let u1 = 10;
let u2 = u1; // u1 value copied(not moved) to u2
println!("u1 = {}",u1);
}
輸出將是 - 10。
Rust - 借用
將變數的所有權傳遞給另一個函式然後返回所有權非常不方便。Rust支援一個概念,即借用,其中值的擁有權暫時轉移到一個實體,然後返回給原始擁有者實體。
考慮以下:
fn main(){
// a list of nos
let v = vec![10,20,30];
print_vector(v);
println!("{}",v[0]); // this line gives error
}
fn print_vector(x:Vec<i32>){
println!("Inside print_vector function {:?}",x);
}
main函式呼叫一個函式print_vector()。將向量作為引數傳遞給此函式。向量的所有權也從main()傳遞給print_vector()函式。當main()函式嘗試訪問向量v時,以上程式碼將導致如下所示的錯誤。
| print_vector(v);
| - value moved here
| println!("{}",v[0]);
| ^ value used here after move
這是因為一旦所有權轉移到另一個函式,最初擁有該變數或值的函式將無法再使用它。
什麼是借用(Borrowing)?
當一個函式暫時(一段時間)將其對變數/值的控制權轉移到另一個函式時,這稱為借用。這是透過傳遞變數的引用(& var_name)而不是將變數/值本身傳遞給函式來實現的。在傳遞控制權的函式執行完成後,變數/值的所有權將轉移回該變數的原始所有者。
fn main(){
// a list of nos
let v = vec![10,20,30];
print_vector(&v); // passing reference
println!("Printing the value from main() v[0]={}",v[0]);
}
fn print_vector(x:&Vec<i32>){
println!("Inside print_vector function {:?}",x);
}
輸出
Inside print_vector function [10, 20, 30] Printing the value from main() v[0] = 10
可變引用(Mutable References)
函式可以透過使用對該資源的可變引用來修改借用的資源。可變引用以&mut為字首。可變引用只能對可變變數進行操作。
示例:修改整數引用
fn add_one(e: &mut i32) {
*e+= 1;
}
fn main() {
let mut i = 3;
add_one(&mut i);
println!("{}", i);
}
main()函式宣告一個可變整數變數i,並將i的可變引用傳遞給add_one()。add_one()將變數i的值加1。
示例:修改字串引用
fn main() {
let mut name:String = String::from("TutorialsPoint");
display(&mut name);
//pass a mutable reference of name
println!("The value of name after modification is:{}",name);
}
fn display(param_name:&mut String){
println!("param_name value is :{}",param_name);
param_name.push_str(" Rocks");
//Modify the actual string,name
}
main()函式將變數name的可變引用傳遞給display()函式。display函式將額外的字串附加到原始name變數。
輸出
param_name value is :TutorialsPoint The value of name after modification is:TutorialsPoint Rocks
Rust - 切片
切片是指向記憶體塊的指標。切片可用於訪問儲存在連續記憶體塊中的資料部分。它可以與陣列、向量和字串等資料結構一起使用。切片使用索引號訪問資料部分。切片的大小在執行時確定。
切片是指向實際資料的指標。它們透過引用傳遞給函式,這也稱為借用。
例如,切片可用於獲取字串值的某一部分。切片字串是指向實際字串物件的指標。因此,我們需要指定字串的起始和結束索引。索引從0開始,就像陣列一樣。
語法
let sliced_value = &data_structure[start_index..end_index]
最小索引值為0,最大索引值為資料結構的大小。注意,最終字串中不包含end_index。
下圖顯示了一個示例字串Tutorials,它包含9個字元。第一個字元的索引為0,最後一個字元的索引為8。
以下程式碼從字串中獲取5個字元(從索引4開始)。
fn main() {
let n1 = "Tutorials".to_string();
println!("length of string is {}",n1.len());
let c1 = &n1[4..9];
// fetches characters at 4,5,6,7, and 8 indexes
println!("{}",c1);
}
輸出
length of string is 9 rials
示例 - 切割整數陣列
main()函式宣告一個包含5個元素的陣列。它呼叫use_slice()函式,並將三個元素的切片(指向data陣列)傳遞給它。切片透過引用傳遞。use_slice()函式列印切片的值及其長度。
fn main(){
let data = [10,20,30,40,50];
use_slice(&data[1..4]);
//this is effectively borrowing elements for a while
}
fn use_slice(slice:&[i32]) {
// is taking a slice or borrowing a part of an array of i32s
println!("length of slice is {:?}",slice.len());
println!("{:?}",slice);
}
輸出
length of slice is 3 [20, 30, 40]
可變切片(Mutable Slices)
&mut關鍵字可用於將切片標記為可變的。
fn main(){
let mut data = [10,20,30,40,50];
use_slice(&mut data[1..4]);
// passes references of
20, 30 and 40
println!("{:?}",data);
}
fn use_slice(slice:&mut [i32]) {
println!("length of slice is {:?}",slice.len());
println!("{:?}",slice);
slice[0] = 1010; // replaces 20 with 1010
}
輸出
length of slice is 3 [20, 30, 40] [10, 1010, 30, 40, 50]
以上程式碼將可變切片傳遞給use_slice()函式。該函式修改原始陣列的第二個元素。
Rust - 結構體
陣列用於表示同構值的集合。類似地,結構體是Rust中另一種使用者定義的資料型別,它允許我們將不同型別的資料項組合在一起,包括另一個結構體。結構體將資料定義為鍵值對。
語法 - 宣告結構體
struct關鍵字用於宣告結構體。由於結構體是靜態型別的,因此結構體中的每個欄位都必須與資料型別相關聯。結構體的命名規則和約定與變數類似。結構體塊必須以分號結尾。
struct Name_of_structure {
field1:data_type,
field2:data_type,
field3:data_type
}
語法 - 初始化結構體
宣告結構體後,應為每個欄位分配一個值。這稱為初始化。
let instance_name = Name_of_structure {
field1:value1,
field2:value2,
field3:value3
};
//NOTE the semicolon
Syntax: Accessing values in a structure
Use the dot notation to access value of a specific field.
instance_name.field1
Illustration
struct Employee {
name:String,
company:String,
age:u32
}
fn main() {
let emp1 = Employee {
company:String::from("TutorialsPoint"),
name:String::from("Mohtashim"),
age:50
};
println!("Name is :{} company is {} age is {}",emp1.name,emp1.company,emp1.age);
}
以上示例聲明瞭一個名為Employee的結構體,它包含三個欄位:name、company和age,分別對應不同的型別。main()初始化了該結構體。它使用println!宏列印結構體中定義的欄位的值。
輸出
Name is :Mohtashim company is TutorialsPoint age is 50
修改結構體例項
要修改例項,應將例項變數標記為可變的。以下示例宣告並初始化了一個名為Employee的結構體,然後將age欄位的值從50修改為40。
let mut emp1 = Employee {
company:String::from("TutorialsPoint"),
name:String::from("Mohtashim"),
age:50
};
emp1.age = 40;
println!("Name is :{} company is {} age is
{}",emp1.name,emp1.company,emp1.age);
輸出
Name is :Mohtashim company is TutorialsPoint age is 40
將結構體傳遞給函式
以下示例演示瞭如何將結構體例項作為引數傳遞。display方法將Employee例項作為引數,並列印詳細資訊。
fn display( emp:Employee) {
println!("Name is :{} company is {} age is
{}",emp.name,emp.company,emp.age);
}
這是完整的程式:
//declare a structure
struct Employee {
name:String,
company:String,
age:u32
}
fn main() {
//initialize a structure
let emp1 = Employee {
company:String::from("TutorialsPoint"),
name:String::from("Mohtashim"),
age:50
};
let emp2 = Employee{
company:String::from("TutorialsPoint"),
name:String::from("Kannan"),
age:32
};
//pass emp1 and emp2 to display()
display(emp1);
display(emp2);
}
// fetch values of specific structure fields using the
// operator and print it to the console
fn display( emp:Employee){
println!("Name is :{} company is {} age is
{}",emp.name,emp.company,emp.age);
}
輸出
Name is :Mohtashim company is TutorialsPoint age is 50 Name is :Kannan company is TutorialsPoint age is 32
從函式返回結構體
讓我們考慮一個函式who_is_elder(),它比較兩個員工的年齡並返回年齡較大的人。
fn who_is_elder (emp1:Employee,emp2:Employee)->Employee {
if emp1.age>emp2.age {
return emp1;
} else {
return emp2;
}
}
這是完整的程式:
fn main() {
//initialize structure
let emp1 = Employee{
company:String::from("TutorialsPoint"),
name:String::from("Mohtashim"),
age:50
};
let emp2 = Employee {
company:String::from("TutorialsPoint"),
name:String::from("Kannan"),
age:32
};
let elder = who_is_elder(emp1,emp2);
println!("elder is:");
//prints details of the elder employee
display(elder);
}
//accepts instances of employee structure and compares their age
fn who_is_elder (emp1:Employee,emp2:Employee)->Employee {
if emp1.age>emp2.age {
return emp1;
} else {
return emp2;
}
}
//display name, comapny and age of the employee
fn display( emp:Employee) {
println!("Name is :{} company is {} age is {}",emp.name,emp.company,emp.age);
}
//declare a structure
struct Employee {
name:String,
company:String,
age:u32
}
輸出
elder is: Name is :Mohtashim company is TutorialsPoint age is 50
結構體中的方法(Method)
方法類似於函式。它們是一組邏輯上的程式設計指令。方法使用fn關鍵字宣告。方法的作用域在結構體塊內。
方法是在結構體塊外部宣告的。impl關鍵字用於在結構體的上下文中定義方法。方法的第一個引數始終為self,它表示結構體的呼叫例項。方法對結構體的資料成員進行操作。
要呼叫方法,我們需要首先例項化結構體。可以使用結構體的例項呼叫方法。
語法
struct My_struct {}
impl My_struct {
//set the method's context
fn method_name() {
//define a method
}
}
說明
以下示例定義了一個名為Rectangle的結構體,其欄位為:width和height。在結構體的上下文中定義了一個名為area的方法。area方法透過self關鍵字訪問結構體的欄位,並計算矩形的面積。
//define dimensions of a rectangle
struct Rectangle {
width:u32, height:u32
}
//logic to calculate area of a rectangle
impl Rectangle {
fn area(&self)->u32 {
//use the . operator to fetch the value of a field via the self keyword
self.width * self.height
}
}
fn main() {
// instanatiate the structure
let small = Rectangle {
width:10,
height:20
};
//print the rectangle's area
println!("width is {} height is {} area of Rectangle
is {}",small.width,small.height,small.area());
}
輸出
width is 10 height is 20 area of Rectangle is 200
結構體中的靜態方法(Static Method)
靜態方法可用作實用程式方法。這些方法在結構體例項化之前就存在。靜態方法使用結構體的名稱呼叫,並且可以在沒有例項的情況下訪問。與普通方法不同,靜態方法不會使用&self引數。
語法 - 宣告靜態方法
靜態方法與函式和其他方法一樣,可以選擇包含引數。
impl Structure_Name {
//static method that creates objects of the Point structure
fn method_name(param1: datatype, param2: datatype) -> return_type {
// logic goes here
}
}
語法 - 呼叫靜態方法
structure_name ::語法用於訪問靜態方法。
structure_name::method_name(v1,v2)
說明
以下示例使用getInstance方法作為工廠類,它建立並返回Point結構體的例項。
//declare a structure
struct Point {
x: i32,
y: i32,
}
impl Point {
//static method that creates objects of the Point structure
fn getInstance(x: i32, y: i32) -> Point {
Point { x: x, y: y }
}
//display values of the structure's field
fn display(&self){
println!("x ={} y={}",self.x,self.y );
}
}
fn main(){
// Invoke the static method
let p1 = Point::getInstance(10,20);
p1.display();
}
輸出
x =10 y=20
Rust - 列舉
在Rust程式設計中,當我們必須從一組可能的變體中選擇一個值時,我們使用列舉資料型別。列舉型別使用enum關鍵字宣告。以下是列舉的語法:
enum enum_name {
variant1,
variant2,
variant3
}
示例:使用列舉
該示例聲明瞭一個列舉:GenderCategory,它具有Male和Female兩個變體。print!宏顯示列舉的值。編譯器將丟擲一個錯誤trait std::fmt::Debug is not implemented for GenderCategory。#[derive(Debug)]屬性用於抑制此錯誤。
// The `derive` attribute automatically creates the implementation
// required to make this `enum` printable with `fmt::Debug`.
#[derive(Debug)]
enum GenderCategory {
Male,Female
}
fn main() {
let male = GenderCategory::Male;
let female = GenderCategory::Female;
println!("{:?}",male);
println!("{:?}",female);
}
輸出
Male Female
結構體和列舉
以下示例定義了一個Person結構體。gender欄位的型別為GenderCategory(這是一個列舉),可以將其賦值為Male或Female。
// The `derive` attribute automatically creates the
implementation
// required to make this `enum` printable with
`fmt::Debug`.
#[derive(Debug)]
enum GenderCategory {
Male,Female
}
// The `derive` attribute automatically creates the implementation
// required to make this `struct` printable with `fmt::Debug`.
#[derive(Debug)]
struct Person {
name:String,
gender:GenderCategory
}
fn main() {
let p1 = Person {
name:String::from("Mohtashim"),
gender:GenderCategory::Male
};
let p2 = Person {
name:String::from("Amy"),
gender:GenderCategory::Female
};
println!("{:?}",p1);
println!("{:?}",p2);
}
該示例建立了p1和p2兩個Person型別的物件,併為每個物件的屬性name和gender進行了初始化。
輸出
Person { name: "Mohtashim", gender: Male }
Person { name: "Amy", gender: Female }
Option列舉
Option是Rust標準庫中預定義的列舉。此列舉有兩個值:Some(data)和None。
語法
enum Option<T> {
Some(T), //used to return a value
None // used to return null, as Rust doesn't support
the null keyword
}
這裡,型別T表示任何型別的值。
Rust不支援null關鍵字。enumOption中的None值可由函式用於返回空值。如果要返回值,則函式可以返回Some(data)。
讓我們用一個例子來理解:
程式定義了一個函式is_even(),其返回型別為Option。該函式驗證傳遞的值是否為偶數。如果輸入為偶數,則返回true,否則函式返回None。
fn main() {
let result = is_even(3);
println!("{:?}",result);
println!("{:?}",is_even(30));
}
fn is_even(no:i32)->Option<bool> {
if no%2 == 0 {
Some(true)
} else {
None
}
}
輸出
None Some(true)
Match語句和列舉
match語句可用於比較儲存在列舉中的值。以下示例定義了一個函式print_size,它將CarType列舉作為引數。該函式將引數值與預定義的一組常量進行比較,並顯示相應的訊息。
enum CarType {
Hatch,
Sedan,
SUV
}
fn print_size(car:CarType) {
match car {
CarType::Hatch => {
println!("Small sized car");
},
CarType::Sedan => {
println!("medium sized car");
},
CarType::SUV =>{
println!("Large sized Sports Utility car");
}
}
}
fn main(){
print_size(CarType::SUV);
print_size(CarType::Hatch);
print_size(CarType::Sedan);
}
輸出
Large sized Sports Utility car Small sized car medium sized car
Option的匹配
返回Option型別的is_even函式的示例也可以使用match語句實現,如下所示:
fn main() {
match is_even(5) {
Some(data) => {
if data==true {
println!("Even no");
}
},
None => {
println!("not even");
}
}
}
fn is_even(no:i32)->Option<bool> {
if no%2 == 0 {
Some(true)
} else {
None
}
}
輸出
not even
Match和帶有資料型別的列舉
可以為列舉的每個變體新增資料型別。在以下示例中,列舉的Name和Usr_ID變體分別為String和整數型別。以下示例演示了使用帶有資料型別的列舉的match語句。
// The `derive` attribute automatically creates the implementation
// required to make this `enum` printable with `fmt::Debug`.
#[derive(Debug)]
enum GenderCategory {
Name(String),Usr_ID(i32)
}
fn main() {
let p1 = GenderCategory::Name(String::from("Mohtashim"));
let p2 = GenderCategory::Usr_ID(100);
println!("{:?}",p1);
println!("{:?}",p2);
match p1 {
GenderCategory::Name(val)=> {
println!("{}",val);
}
GenderCategory::Usr_ID(val)=> {
println!("{}",val);
}
}
}
輸出
Name("Mohtashim")
Usr_ID(100)
Mohtashim
Rust - 模組
程式碼的邏輯組稱為模組。多個模組編譯成一個稱為crate的單元。Rust程式可能包含一個二進位制crate或一個庫crate。二進位制crate是一個可執行專案,它具有main()方法。庫crate是一組可以在其他專案中重用的元件。與二進位制crate不同,庫crate沒有入口點(main()方法)。Cargo工具用於管理Rust中的crate。例如,network模組包含網路相關的函式,而graphics模組包含繪圖相關的函式。模組類似於其他程式語言中的名稱空間。可以使用cargo從crates.io下載第三方crate。
| 序號 | 術語和描述 |
|---|---|
| 1 | crate 是Rust中的編譯單元;Crate編譯成二進位制檔案或庫。 |
| 2 | cargo crate的官方Rust包管理工具。 |
| 3 | module 在crate中邏輯地對程式碼進行分組。 |
| 4 |
官方Rust包登錄檔。 |
語法
//public module
pub mod a_public_module {
pub fn a_public_function() {
//public function
}
fn a_private_function() {
//private function
}
}
//private module
mod a_private_module {
fn a_private_function() {
}
}
模組可以是公共的或私有的。私有模組中的元件無法被其他模組訪問。Rust中的模組預設是私有的。相反,公共模組中的函式可以被其他模組訪問。模組應該以pub關鍵字為字首才能使其成為公共的。公共模組中的函式也必須是公共的。
示例:定義模組
該示例定義了一個公共模組:movies。該模組包含一個函式play(),它接受一個引數並列印其值。
pub mod movies {
pub fn play(name:String) {
println!("Playing movie {}",name);
}
}
fn main(){
movies::play("Herold and Kumar".to_string());
}
輸出
Playing movie Herold and Kumar
Use關鍵字
use關鍵字有助於匯入公共模組。
語法
use public_module_name::function_name;
說明
pub mod movies {
pub fn play(name:String) {
println!("Playing movie {}",name);
}
}
use movies::play;
fn main(){
play("Herold and Kumar ".to_string());
}
輸出
Playing movie Herold and Kumar
巢狀模組
模組也可以巢狀。comedy模組巢狀在english模組中,後者進一步巢狀在movies模組中。以下示例在movies/english/comedy模組中定義了一個函式play。
pub mod movies {
pub mod english {
pub mod comedy {
pub fn play(name:String) {
println!("Playing comedy movie {}",name);
}
}
}
}
use movies::english::comedy::play;
// importing a public module
fn main() {
// short path syntax
play("Herold and Kumar".to_string());
play("The Hangover".to_string());
//full path syntax
movies::english::comedy::play("Airplane!".to_string());
}
輸出
Playing comedy movie Herold and Kumar Playing comedy movie The Hangover Playing comedy movie Airplane!
示例 - 建立庫crate並在二進位制crate中使用
讓我們建立一個名為movie_lib的庫箱,其中包含一個模組movies。為了構建movie_lib庫箱,我們將使用工具cargo。
步驟 1 - 建立專案資料夾
建立一個名為movie-app的資料夾,並在其下建立一個名為movie-lib的子資料夾。建立資料夾和子資料夾後,在此目錄中建立一個src資料夾和一個Cargo.toml檔案。原始碼應放在src資料夾中。在src資料夾中建立lib.rs和movies.rs檔案。Cargo.toml檔案將包含專案元資料,例如版本號、作者姓名等。
專案目錄結構如下所示:
movie-app
movie-lib/
-->Cargo.toml
-->src/
lib.rs
movies.rs
步驟 2 - 編輯Cargo.toml檔案以新增專案元資料
[package] name = "movies_lib" version = "0.1.0" authors = ["Mohtashim"]
步驟 3 - 編輯lib.rs檔案。
將以下模組定義新增到此檔案中。
pub mod movies;
以上程式碼建立了一個公共模組 - movies。
步驟 4 - 編輯movies.rs檔案
此檔案將定義movies模組的所有函式。
pub fn play(name:String){
println!("Playing movie {} :movies-app",name);
}
以上程式碼定義了一個名為play()的函式,該函式接受一個引數並將其列印到控制檯。
步驟 5 - 構建庫箱
使用cargo build命令構建應用程式,以驗證庫箱的結構是否正確。確保您位於專案的根目錄 - movie-app資料夾。如果構建成功,終端將顯示以下訊息。
D:\Rust\movie-lib> cargo build Compiling movies_lib v0.1.0 (file:///D:/Rust/movie-lib) Finished dev [unoptimized + debuginfo] target(s) in 0.67s
步驟 6 - 建立測試應用程式
在movie-app資料夾中建立另一個名為movie-lib-test的資料夾,並在其下建立一個Cargo.toml檔案和src資料夾。此專案應該具有main方法,因為這是一個二進位制箱,它將使用之前建立的庫箱。在src資料夾中建立一個main.rs檔案。資料夾結構如下所示。
movie-app
movie-lib
// already completed
movie-lib-test/
-->Cargo.toml
-->src/
main.rs
步驟 7 - 在Cargo.toml檔案中新增以下內容
[package]
name = "test_for_movie_lib"
version = "0.1.0"
authors = ["Mohtashim"]
[dependencies]
movies_lib = { path = "../movie-lib" }
注意 - 庫資料夾的路徑設定為依賴項。下圖顯示了這兩個專案的目錄。
步驟 8 - 將以下內容新增到main.rs檔案
extern crate movies_lib;
use movies_lib::movies::play;
fn main() {
println!("inside main of test ");
play("Tutorialspoint".to_string())
}
以上程式碼匯入了一個名為movies_lib的外部包。檢查當前專案的Cargo.toml以驗證箱名。
步驟 9 - 使用cargo build和cargo run
我們將使用cargo build和cargo run來構建二進位制專案並執行它,如下所示:
Rust - 集合
Rust的標準集合庫提供了最常見的通用程式設計資料結構的高效實現。本章討論了常用集合 - 向量、雜湊表和雜湊集的實現。
向量
向量是一個可調整大小的陣列。它將值儲存在連續的記憶體塊中。預定義結構Vec可用於建立向量。向量的一些重要特性包括:
向量可以在執行時增長或縮小。
向量是同構集合。
向量按特定順序儲存資料作為元素序列。向量中的每個元素都分配一個唯一的索引號。索引從0開始,一直到n-1,其中n是集合的大小。例如,在一個包含5個元素的集合中,第一個元素的索引為0,最後一個元素的索引為4。
向量只會將值追加到(或接近)末尾。換句話說,向量可用於實現堆疊。
向量的記憶體分配在堆上。
語法 - 建立向量
let mut instance_name = Vec::new();
Vec結構的靜態方法new()用於建立向量例項。
或者,也可以使用vec!宏建立向量。語法如下所示:
let vector_name = vec![val1,val2,val3]
下表列出了Vec結構的一些常用函式。
| 序號 | 方法 | 簽名和描述 |
|---|---|---|
| 1 | new() | pub fn new()->Vect 構造一個新的空Vec。在元素被推入之前,向量不會分配記憶體。 |
| 2 | push() | pub fn push(&mut self, value: T) 將元素追加到集合的末尾。 |
| 3 | remove() | pub fn remove(&mut self, index: usize) -> T 移除並返回向量中索引位置處的元素,並將所有後續元素向左移動。 |
| 4 | contains() | pub fn contains(&self, x: &T) -> bool 如果切片包含具有給定值的元素,則返回true。 |
| 5 | len() | pub fn len(&self) -> usize 返回向量中的元素數量,也稱為其“長度”。 |
圖解:建立向量 - new()
要建立向量,我們使用靜態方法new -
fn main() {
let mut v = Vec::new();
v.push(20);
v.push(30);
v.push(40);
println!("size of vector is :{}",v.len());
println!("{:?}",v);
}
以上示例使用Vec結構中定義的靜態方法new()建立向量。push(val)函式將作為引數傳遞的值追加到集合中。len()函式返回向量的長度。
輸出
size of vector is :3 [20, 30, 40]
圖解:建立向量 - vec!宏
以下程式碼使用vec!宏建立向量。向量的型別由分配給它的第一個值推斷得出。
fn main() {
let v = vec![1,2,3];
println!("{:?}",v);
}
輸出
[1, 2, 3]
如前所述,向量只能包含相同資料型別的值。以下程式碼段將引發error[E0308]: mismatched types錯誤。
fn main() {
let v = vec![1,2,3,"hello"];
println!("{:?}",v);
}
圖示:push()
將元素追加到集合的末尾。
fn main() {
let mut v = Vec::new();
v.push(20);
v.push(30);
v.push(40);
println!("{:?}",v);
}
輸出
[20, 30, 40]
圖解:remove()
移除並返回向量中索引位置處的元素,並將所有後續元素向左移動。
fn main() {
let mut v = vec![10,20,30];
v.remove(1);
println!("{:?}",v);
}
輸出
[10, 30]
圖解 - contains()
如果切片包含具有給定值的元素,則返回true -
fn main() {
let v = vec![10,20,30];
if v.contains(&10) {
println!("found 10");
}
println!("{:?}",v);
}
輸出
found 10 [10, 20, 30]
圖示:len()
返回向量中的元素數量,也稱為其“長度”。
fn main() {
let v = vec![1,2,3];
println!("size of vector is :{}",v.len());
}
輸出
size of vector is :3
從向量中訪問值
可以使用相應的索引號訪問向量中的各個元素。以下示例建立一個向量並列印第一個元素的值。
fn main() {
let mut v = Vec::new();
v.push(20);
v.push(30);
println!("{:?}",v[0]);
}
Output: `20`
也可以使用對集合的引用來獲取向量中的值。
fn main() {
let mut v = Vec::new();
v.push(20);
v.push(30);
v.push(40);
v.push(500);
for i in &v {
println!("{}",i);
}
println!("{:?}",v);
}
輸出
20 30 40 500 [20, 30, 40, 500]
雜湊表
對映是鍵值對(稱為條目)的集合。對映中沒有兩個條目可以具有相同的鍵。簡而言之,對映是一個查詢表。雜湊表將鍵和值儲存在雜湊表中。條目按任意順序儲存。鍵用於在雜湊表中搜索值。HashMap結構在std::collections模組中定義。應顯式匯入此模組以訪問HashMap結構。
語法:建立雜湊表
let mut instance_name = HashMap::new();
HashMap結構的靜態方法new()用於建立HashMap物件。此方法建立一個空的HashMap。
下面討論了HashMap的常用函式:
| 序號 | 方法 | 簽名和描述 |
|---|---|---|
| 1 | insert() | pub fn insert(&mut self, k: K, v: V) -> Option 插入鍵值對,如果不存在鍵,則返回None。更新後,返回舊值。 |
| 2 | len() | pub fn len(&self) -> usize 返回對映中的元素數量。 |
| 3 | get() | pub fn get<Q: ?Sized>(&lself, k: &Q) -> Option<&V> where K:Borrow Q:Hash+ Eq 返回對應於鍵的值的引用。 |
| 4 | iter() | pub fn iter(&self) -> Iter<K, V> 一個以任意順序訪問所有鍵值對的迭代器。迭代器元素型別為(&'a K, &'a V)。 |
| 5 | contains_key | pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool 如果對映包含指定鍵的值,則返回true。 |
| 6 | remove() | pub fn remove_entry<Q: ?Sized>(&mut self, k: &Q) -> Option<(K, V)> 從對映中移除鍵,如果鍵先前在對映中,則返回儲存的鍵和值。 |
圖解:insert()
將鍵值對插入HashMap中。
use std::collections::HashMap;
fn main(){
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
println!("{:?}",stateCodes);
}
以上程式建立了一個HashMap並使用2個鍵值對對其進行初始化。
輸出
{"KL": "Kerala", "MH": "Maharashtra"}
圖示:len()
返回對映中的元素數量
use std::collections::HashMap;
fn main() {
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
println!("size of map is {}",stateCodes.len());
}
以上示例建立了一個HashMap並列印其中的元素總數。
輸出
size of map is 2
圖解 - get()
返回對應於鍵的值的引用。以下示例檢索HashMap中鍵KL的值。
use std::collections::HashMap;
fn main() {
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
println!("size of map is {}",stateCodes.len());
println!("{:?}",stateCodes);
match stateCodes.get(&"KL") {
Some(value)=> {
println!("Value for key KL is {}",value);
}
None => {
println!("nothing found");
}
}
}
輸出
size of map is 2
{"KL": "Kerala", "MH": "Maharashtra"}
Value for key KL is Kerala
圖解 - iter()
返回一個包含對所有鍵值對的引用的迭代器,這些鍵值對按任意順序排列。
use std::collections::HashMap;
fn main() {
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
for (key, val) in stateCodes.iter() {
println!("key: {} val: {}", key, val);
}
}
輸出
key: MH val: Maharashtra key: KL val: Kerala
圖解:contains_key()
如果對映包含指定鍵的值,則返回true。
use std::collections::HashMap;
fn main() {
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
stateCodes.insert("GJ","Gujarat");
if stateCodes.contains_key(&"GJ") {
println!("found key");
}
}
輸出
found key
圖解:remove()
從對映中移除鍵。
use std::collections::HashMap;
fn main() {
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
stateCodes.insert("GJ","Gujarat");
println!("length of the hashmap {}",stateCodes.len());
stateCodes.remove(&"GJ");
println!("length of the hashmap after remove() {}",stateCodes.len());
}
輸出
length of the hashmap 3 length of the hashmap after remove() 2
雜湊集
雜湊集是型別T的唯一值的集合。新增和移除值很快,並且快速詢問給定值是否在集合中。HashSet結構在std::collections模組中定義。應顯式匯入此模組以訪問HashSet結構。
語法:建立雜湊集
let mut hash_set_name = HashSet::new();
HashSet結構的靜態方法new用於建立雜湊集。此方法建立一個空的雜湊集。
下表列出了HashSet結構的一些常用方法。
| 序號 | 方法 | 簽名和描述 |
|---|---|---|
| 1 | insert() | pub fn insert(&mut self, value: T) -> bool 將值新增到集合中。如果集合中不存在此值,則返回true,否則返回false。 |
| 2 | len() | pub fn len(&self) -> usize 返回集合中的元素數量。 |
| 3 | get() | pub fn get<Q:?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow,Q: Hash + Eq, 返回集合中與給定值相等的值的引用(如果有)。 |
| 4 | iter() | pub fn iter(&self) -> Iter 返回一個以任意順序訪問所有元素的迭代器。迭代器元素型別為&'a T。 |
| 5 | contains_key | pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool 如果集合包含值,則返回true。 |
| 6 | remove() | pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool 從集合中移除值。如果該值存在於集合中,則返回true。 |
圖解 - insert()
將值新增到集合中。雜湊集不會將重複值新增到集合中。
use std::collections::HashSet;
fn main() {
let mut names = HashSet::new();
names.insert("Mohtashim");
names.insert("Kannan");
names.insert("TutorialsPoint");
names.insert("Mohtashim");//duplicates not added
println!("{:?}",names);
}
輸出
{"TutorialsPoint", "Kannan", "Mohtashim"}
圖示:len()
返回集合中的元素數量。
use std::collections::HashSet;
fn main() {
let mut names = HashSet::new();
names.insert("Mohtashim");
names.insert("Kannan");
names.insert("TutorialsPoint");
println!("size of the set is {}",names.len());
}
輸出
size of the set is 3
圖解 - iter()
返回一個以任意順序訪問所有元素的迭代器。
use std::collections::HashSet;
fn main() {
let mut names = HashSet::new();
names.insert("Mohtashim");
names.insert("Kannan");
names.insert("TutorialsPoint");
names.insert("Mohtashim");
for name in names.iter() {
println!("{}",name);
}
}
輸出
TutorialsPoint Mohtashim Kannan
圖解:get()
返回集合中與給定值相等的值的引用(如果有)。
use std::collections::HashSet;
fn main() {
let mut names = HashSet::new();
names.insert("Mohtashim");
names.insert("Kannan");
names.insert("TutorialsPoint");
names.insert("Mohtashim");
match names.get(&"Mohtashim"){
Some(value)=>{
println!("found {}",value);
}
None =>{
println!("not found");
}
}
println!("{:?}",names);
}
輸出
found Mohtashim
{"Kannan", "Mohtashim", "TutorialsPoint"}
圖解 - contains()
如果集合包含值,則返回true。
use std::collections::HashSet;
fn main() {
let mut names = HashSet::new();
names.insert("Mohtashim");
names.insert("Kannan");
names.insert("TutorialsPoint");
if names.contains(&"Kannan") {
println!("found name");
}
}
輸出
found name
圖解:remove()
從集合中移除值。
use std::collections::HashSet;
fn main() {
let mut names = HashSet::new();
names.insert("Mohtashim");
names.insert("Kannan");
names.insert("TutorialsPoint");
println!("length of the Hashset: {}",names.len());
names.remove(&"Kannan");
println!("length of the Hashset after remove() : {}",names.len());
}
輸出
length of the Hashset: 3 length of the Hashset after remove() : 2
Rust - 錯誤處理
在Rust中,錯誤可以分為兩大類,如下表所示。
| 序號 | 名稱和描述 | 用法 |
|---|---|---|
| 1 | 可恢復 可以處理的錯誤 | Result列舉 |
| 2 | 不可恢復 無法處理的錯誤 | panic宏 |
可恢復錯誤是可以糾正的錯誤。程式可以在遇到可恢復錯誤時重試失敗的操作或指定備用操作方案。可恢復錯誤不會導致程式突然失敗。可恢復錯誤的一個示例是檔案未找到錯誤。
不可恢復錯誤會導致程式突然失敗。如果發生不可恢復錯誤,程式無法恢復到其正常狀態。它無法重試失敗的操作或撤消錯誤。不可恢復錯誤的一個示例是嘗試訪問陣列末尾之外的位置。
與其他程式語言不同,Rust沒有異常。它為可恢復錯誤返回列舉Result<T, E>,而在程式遇到不可恢復錯誤時呼叫panic宏。panic宏會導致程式突然退出。
Panic宏和不可恢復錯誤
panic!宏允許程式立即終止並向程式的呼叫者提供反饋。當程式達到不可恢復狀態時,應使用它。
fn main() {
panic!("Hello");
println!("End of main"); //unreachable statement
}
在以上示例中,當程式遇到panic!宏時,它將立即終止。
輸出
thread 'main' panicked at 'Hello', main.rs:3
圖解:panic!宏
fn main() {
let a = [10,20,30];
a[10]; //invokes a panic since index 10 cannot be reached
}
輸出如下所示:
warning: this expression will panic at run-time --> main.rs:4:4 | 4 | a[10]; | ^^^^^ index out of bounds: the len is 3 but the index is 10 $main thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 10', main.rs:4 note: Run with `RUST_BACKTRACE=1` for a backtrace.
如果違反業務規則,程式可以呼叫panic!宏,如下例所示:
fn main() {
let no = 13;
//try with odd and even
if no%2 == 0 {
println!("Thank you , number is even");
} else {
panic!("NOT_AN_EVEN");
}
println!("End of main");
}
以上示例如果變數分配的值為奇數,則返回錯誤。
輸出
thread 'main' panicked at 'NOT_AN_EVEN', main.rs:9 note: Run with `RUST_BACKTRACE=1` for a backtrace.
Result列舉和可恢復錯誤
列舉Result – <T,E>可用於處理可恢復錯誤。它有兩個變體 - OK和Err。T和E是泛型型別引數。T表示在成功情況下將在OK變體中返回的值的型別,E表示在失敗情況下將在Err變體中返回的錯誤的型別。
enum Result<T,E> {
OK(T),
Err(E)
}
讓我們藉助示例來理解這一點:
use std::fs::File;
fn main() {
let f = File::open("main.jpg");
//this file does not exist
println!("{:?}",f);
}
如果檔案已存在,程式返回OK(File);如果檔案未找到,則返回Err(Error)。
Err(Error { repr: Os { code: 2, message: "No such file or directory" } })
現在讓我們看看如何處理 Err 變體。
以下示例使用match語句處理開啟檔案時返回的錯誤
use std::fs::File;
fn main() {
let f = File::open("main.jpg"); // main.jpg doesn't exist
match f {
Ok(f)=> {
println!("file found {:?}",f);
},
Err(e)=> {
println!("file not found \n{:?}",e); //handled error
}
}
println!("end of main");
}
注意 - 儘管未找到檔案,但程式仍然列印了main事件的end。這意味著程式已優雅地處理了錯誤。
輸出
file not found
Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." }
end of main
說明
is_even函式如果數字不是偶數,則返回錯誤。main() 函式處理此錯誤。
fn main(){
let result = is_even(13);
match result {
Ok(d)=>{
println!("no is even {}",d);
},
Err(msg)=>{
println!("Error msg is {}",msg);
}
}
println!("end of main");
}
fn is_even(no:i32)->Result<bool,String> {
if no%2==0 {
return Ok(true);
} else {
return Err("NOT_AN_EVEN".to_string());
}
}
注意 - 由於 main 函式優雅地處理了錯誤,因此列印了main語句的end。
輸出
Error msg is NOT_AN_EVEN end of main
unwrap() 和 expect()
標準庫包含幾個輔助方法,Result<T,E>和Option<T>列舉都實現了這些方法。您可以使用它們來簡化您確實不希望出現錯誤的錯誤情況。如果方法成功,則使用“unwrap”函式提取實際結果。
| 序號 | 方法 | 簽名和描述 |
|---|---|---|
| 1 | unwrap | unwrap(self): T 期望 self 為 Ok/Some 並返回其中包含的值。如果它改為Err或None,則會引發 panic,並顯示錯誤內容。 |
| 2 | expect | expect(self, msg: &str): T 行為類似於 unwrap,除了在出現 panic 之前除了錯誤內容之外還會輸出自定義訊息。 |
unwrap()
unwrap() 函式在操作成功時返回實際結果。如果操作失敗,則返回帶有預設錯誤訊息的 panic。此函式是 match 語句的簡寫。如下面的示例所示 -
fn main(){
let result = is_even(10).unwrap();
println!("result is {}",result);
println!("end of main");
}
fn is_even(no:i32)->Result<bool,String> {
if no%2==0 {
return Ok(true);
} else {
return Err("NOT_AN_EVEN".to_string());
}
}
result is true
end of main
修改上述程式碼,將奇數傳遞給is_even()函式。
unwrap()函式將出現 panic 並返回如下所示的預設錯誤訊息
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "NOT_AN_EVEN"', libcore\result.rs:945:5 note: Run with `RUST_BACKTRACE=1` for a backtrace
expect()
程式可以在出現 panic 的情況下返回自定義錯誤訊息。以下示例顯示了這一點 -
use std::fs::File;
fn main(){
let f = File::open("pqr.txt").expect("File not able to open");
//file does not exist
println!("end of main");
}
expect() 函式類似於 unwrap()。唯一的區別是可以使用 expect 顯示自定義錯誤訊息。
輸出
thread 'main' panicked at 'File not able to open: Error { repr: Os
{ code: 2, message: "No such file or directory" } }', src/libcore/result.rs:860
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Rust - 泛型
泛型是為具有不同型別的多個上下文編寫程式碼的一種方法。在 Rust 中,泛型指的是資料型別和特徵的引數化。泛型允許透過減少程式碼重複和提供型別安全來編寫更簡潔和乾淨的程式碼。泛型的概念可以應用於方法、函式、結構體、列舉、集合和特徵。
<T> 語法稱為型別引數,用於宣告泛型構造。T表示任何資料型別。
插圖:泛型集合
以下示例聲明瞭一個只能儲存整數的向量。
fn main(){
let mut vector_integer: Vec<i32> = vec![20,30];
vector_integer.push(40);
println!("{:?}",vector_integer);
}
輸出
[20, 30, 40]
考慮以下程式碼段:
fn main() {
let mut vector_integer: Vec<i32> = vec![20,30];
vector_integer.push(40);
vector_integer.push("hello");
//error[E0308]: mismatched types
println!("{:?}",vector_integer);
}
上面的示例表明,整數型別的向量只能儲存整數值。因此,如果我們嘗試將字串值推入集合,編譯器將返回錯誤。泛型使集合更具型別安全性。
插圖:泛型結構體
型別引數表示一種型別,編譯器稍後將填充該型別。
struct Data<T> {
value:T,
}
fn main() {
//generic type of i32
let t:Data<i32> = Data{value:350};
println!("value is :{} ",t.value);
//generic type of String
let t2:Data<String> = Data{value:"Tom".to_string()};
println!("value is :{} ",t2.value);
}
上面的示例聲明瞭一個名為Data的泛型結構體。<T>型別表示某種資料型別。main()函式建立了兩個例項 - 結構體的整數例項和字串例項。
輸出
value is :350 value is :Tom
特徵
特徵可用於在多個結構體中實現一組標準的行為(方法)。特徵就像面向物件程式設計中的介面。特徵的語法如下所示 -
宣告一個特徵
trait some_trait {
//abstract or method which is empty
fn method1(&self);
// this is already implemented , this is free
fn method2(&self){
//some contents of method2
}
}
特徵可以包含具體方法(帶主體的方法)或抽象方法(不帶主體的方法)。如果方法定義將由實現該特徵的所有結構體共享,請使用具體方法。但是,結構體可以選擇覆蓋特徵定義的函式。
如果方法定義對於實現結構體有所不同,請使用抽象方法。
語法 - 實現特徵
impl some_trait for structure_name {
// implement method1() there..
fn method1(&self ){
}
}
以下示例定義了一個帶有一個print()方法的Printable特徵,該方法由book結構體實現。
fn main(){
//create an instance of the structure
let b1 = Book {
id:1001,
name:"Rust in Action"
};
b1.print();
}
//declare a structure
struct Book {
name:&'static str,
id:u32
}
//declare a trait
trait Printable {
fn print(&self);
}
//implement the trait
impl Printable for Book {
fn print(&self){
println!("Printing book with id:{} and name {}",self.id,self.name)
}
}
輸出
Printing book with id:1001 and name Rust in Action
泛型函式
該示例定義了一個泛型函式,該函式顯示傳遞給它的引數。引數可以是任何型別。引數的型別應實現 Display 特徵,以便其值可以由 println! 宏列印。
use std::fmt::Display;
fn main(){
print_pro(10 as u8);
print_pro(20 as u16);
print_pro("Hello TutorialsPoint");
}
fn print_pro<T:Display>(t:T){
println!("Inside print_pro generic function:");
println!("{}",t);
}
輸出
Inside print_pro generic function: 10 Inside print_pro generic function: 20 Inside print_pro generic function: Hello TutorialsPoint
Rust - 輸入輸出
本章討論如何從標準輸入(鍵盤)接受值並將值顯示到標準輸出(控制檯)。在本章中,我們還將討論傳遞命令列引數。
讀取器和寫入器型別
Rust 的標準庫用於輸入和輸出的功能圍繞兩個特徵組織 -
- 讀取
- 寫入
| 序號 | 特徵和描述 | 示例 |
|---|---|---|
| 1 | 讀取 實現 Read 的型別具有用於面向位元組的輸入的方法。它們被稱為讀取器 | Stdin,File |
| 2 | 寫入 實現 Write 的型別支援面向位元組和 UTF-8 文字輸出。它們被稱為寫入器。 | Stdout,File |
讀取特徵
讀取器是程式可以從中讀取位元組的元件。例如,從鍵盤、檔案等讀取輸入。此特徵的read_line()方法可用於一次讀取一行資料,來自檔案或標準輸入流。
| 序號 | 特徵 | 方法和描述 |
|---|---|---|
| 1 | 讀取 | read_line(&mut line)->Result 讀取一行文字並將其追加到 line,line 是一個字串。返回值是 io::Result,讀取的位元組數。 |
插圖 - 從控制檯讀取 - stdin()
Rust 程式可能需要在執行時接受使用者的值。以下示例從標準輸入(鍵盤)讀取值並將其列印到控制檯。
fn main(){
let mut line = String::new();
println!("Enter your name :");
let b1 = std::io::stdin().read_line(&mut line).unwrap();
println!("Hello , {}", line);
println!("no of bytes read , {}", b1);
}
stdin()函式返回當前程序的標準輸入流的控制代碼,read_line函式可以應用於該控制代碼。此函式嘗試在遇到換行符時讀取輸入緩衝區中存在的所有字元。
輸出
Enter your name : Mohtashim Hello , Mohtashim no of bytes read , 10
寫入特徵
寫入器是程式可以向其寫入位元組的元件。例如,將值列印到控制檯、寫入檔案等。此特徵的 write() 方法可用於將資料寫入檔案或標準輸出流。
| 序號 | 特徵 | 方法和描述 |
|---|---|---|
| 1 | 寫入 | write(&buf)->Result 將切片 buf 中的一些位元組寫入底層流。它返回 io::Result,寫入的位元組數。 |
插圖 - 寫入控制檯 - stdout()
print!或println!宏可用於在控制檯上顯示文字。但是,您也可以使用write()標準庫函式將某些文字顯示到標準輸出。
讓我們考慮一個例子來理解這一點。
use std::io::Write;
fn main() {
let b1 = std::io::stdout().write("Tutorials ".as_bytes()).unwrap();
let b2 = std::io::stdout().write(String::from("Point").as_bytes()).unwrap();
std::io::stdout().write(format!("\nbytes written {}",(b1+b2)).as_bytes()).unwrap();
}
輸出
Tutorials Point bytes written 15
stdout()標準庫函式返回當前程序的標準輸出流的控制代碼,write函式可以應用於該控制代碼。write() 方法返回一個列舉 Result。unwrap() 是一個輔助方法,用於從列舉中提取實際結果。如果發生錯誤,unwrap 方法將傳送 panic。
注意 - 檔案 IO 在下一章中討論。
命令列引數
命令列引數在執行程式之前傳遞給程式。它們就像傳遞給函式的引數一樣。命令列引數可用於將值傳遞給main()函式。std::env::args()返回命令列引數。
說明
以下示例將值作為命令列引數傳遞給 main() 函式。程式在一個名為main.rs的檔案中建立。
//main.rs
fn main(){
let cmd_line = std::env::args();
println!("No of elements in arguments is :{}",cmd_line.len());
//print total number of values passed
for arg in cmd_line {
println!("[{}]",arg); //print all values passed
as commandline arguments
}
}
編譯後,程式將生成一個main.exe檔案。多個命令列引數應以空格分隔。從終端執行 main.exe,如main.exe hello tutorialspoint。
注意 - hello和tutorialspoint是命令列引數。
輸出
No of elements in arguments is :3 [main.exe] [hello] [tutorialspoint]
輸出顯示 3 個引數,因為main.exe是第一個引數。
說明
以下程式計算作為命令列引數傳遞的值的總和。一系列以空格分隔的整數被傳遞給程式。
fn main(){
let cmd_line = std::env::args();
println!("No of elements in arguments is
:{}",cmd_line.len());
// total number of elements passed
let mut sum = 0;
let mut has_read_first_arg = false;
//iterate through all the arguments and calculate their sum
for arg in cmd_line {
if has_read_first_arg { //skip the first argument since it is the exe file name
sum += arg.parse::<i32>().unwrap();
}
has_read_first_arg = true;
// set the flag to true to calculate sum for the subsequent arguments.
}
println!("sum is {}",sum);
}
執行程式時,如 main.exe 1 2 3 4,輸出將為 -
No of elements in arguments is :5 sum is 10
Rust - 檔案輸入/輸出
除了讀取和寫入控制檯外,Rust 還允許讀取和寫入檔案。
File 結構體表示一個檔案。它允許程式對檔案執行讀寫操作。File 結構體中的所有方法都返回 io::Result 列舉的一個變體。
File 結構體常用的方法列在下表中 -
| 序號 | 模組 | 方法 | 簽名 | 描述 |
|---|---|---|---|---|
| 1 | std::fs::File | open() | pub fn open<P: AsRef>(path: P) -> Result | open 靜態方法可用於以只讀模式開啟檔案。 |
| 2 | std::fs::File | create() | pub fn create<P: AsRef>(path: P) -> Result | 靜態方法以寫模式開啟檔案。如果檔案已存在,則舊內容將被銷燬。否則,將建立一個新檔案。 |
| 3 | std::fs::remove_file | remove_file() | pub fn remove_file<P: AsRef>(path: P) -> Result<()> | 從檔案系統中刪除檔案。不能保證檔案會立即被刪除。 |
| 4 | std::fs::OpenOptions | append() | pub fn append(&mut self, append: bool) -> &mut OpenOptions | 設定檔案的追加模式選項。 |
| 5 | std::io::Writes | write_all() | fn write_all(&mut self, buf: &[u8]) -> Result<()> | 嘗試將整個緩衝區寫入此寫入器。 |
| 6 | std::io::Read | read_to_string() | fn read_to_string(&mut self, buf: &mut String) -> Result | 讀取此源中的所有位元組直到 EOF,並將它們追加到 buf。 |
寫入檔案
讓我們看一個示例來了解如何寫入檔案。
以下程式建立一個檔案“data.txt”。create() 方法用於建立檔案。如果檔案建立成功,則該方法返回檔案控制代碼。最後一行write_all函式將位元組寫入新建立的檔案。如果任何操作失敗,expect() 函式將返回錯誤訊息。
use std::io::Write;
fn main() {
let mut file = std::fs::File::create("data.txt").expect("create failed");
file.write_all("Hello World".as_bytes()).expect("write failed");
file.write_all("\nTutorialsPoint".as_bytes()).expect("write failed");
println!("data written to file" );
}
輸出
data written to file
從檔案讀取
以下程式讀取檔案 data.txt 中的內容並將其列印到控制檯。“open”函式用於開啟現有檔案。檔案絕對或相對路徑作為引數傳遞給 open() 函式。如果檔案不存在或由於任何原因無法訪問,則 open() 函式會丟擲異常。如果成功,則將此類檔案的控制代碼分配給“file”變數。
“file”控制代碼的“read_to_string”函式用於將該檔案的內容讀取到字串變數中。
use std::io::Read;
fn main(){
let mut file = std::fs::File::open("data.txt").unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
print!("{}", contents);
}
輸出
Hello World TutorialsPoint
刪除檔案
以下示例使用 remove_file() 函式刪除檔案。如果發生錯誤,expect() 函式將返回自定義訊息。
use std::fs;
fn main() {
fs::remove_file("data.txt").expect("could not remove file");
println!("file is removed");
}
輸出
file is removed
將資料追加到檔案
append() 函式將資料寫入檔案的末尾。這在以下示例中顯示 -
use std::fs::OpenOptions;
use std::io::Write;
fn main() {
let mut file = OpenOptions::new().append(true).open("data.txt").expect(
"cannot open file");
file.write_all("Hello World".as_bytes()).expect("write failed");
file.write_all("\nTutorialsPoint".as_bytes()).expect("write failed");
println!("file append success");
}
輸出
file append success
複製檔案
以下示例將檔案中的內容複製到新檔案。
use std::io::Read;
use std::io::Write;
fn main() {
let mut command_line: std::env::Args = std::env::args();
command_line.next().unwrap();
// skip the executable file name
// accept the source file
let source = command_line.next().unwrap();
// accept the destination file
let destination = command_line.next().unwrap();
let mut file_in = std::fs::File::open(source).unwrap();
let mut file_out = std::fs::File::create(destination).unwrap();
let mut buffer = [0u8; 4096];
loop {
let nbytes = file_in.read(&mut buffer).unwrap();
file_out.write(&buffer[..nbytes]).unwrap();
if nbytes < buffer.len() { break; }
}
}
執行上述程式時,如main.exe data.txt datacopy.txt。執行檔案時傳遞兩個命令列引數 -
- 原始檔的路徑
- 目標檔案
Rust - 包管理器
Cargo 是 RUST 的包管理器。它充當工具並管理 Rust 專案。
下表列出了一些常用的 Cargo 命令 -
| 序號 | 命令和描述 |
|---|---|
| 1 | cargo build 編譯當前專案。 |
| 2 | cargo check 分析當前專案並報告錯誤,但不構建目標檔案。 |
| 3 | cargo run 構建並執行 src/main.rs。 |
| 4 | cargo clean 刪除 target 目錄。 |
| 5 | cargo update 更新 Cargo.lock 中列出的依賴項。 |
| 6 | cargo new 建立一個新的 Cargo 專案。 |
Cargo 幫助下載第三方庫。因此,它充當包管理器。您還可以構建自己的庫。安裝 Rust 時,Cargo 會預設安裝。
要建立新的 Cargo 專案,我們可以使用以下命令。
建立二進位制板條箱
cargo new project_name --bin
建立庫板條箱
cargo new project_name --lib
要檢查 Cargo 的當前版本,請執行以下命令 -
cargo --version
插圖 - 建立二進位制 Cargo 專案
遊戲生成一個隨機數,並提示使用者猜測這個數字。
步驟 1 - 建立專案資料夾
開啟終端並輸入以下命令 cargo new guess-game-app --bin。
這將建立以下資料夾結構。
guess-game-app/
-->Cargo.toml
-->src/
main.rs
cargo new 命令用於建立一個板條箱。--bin 標誌表示建立的板條箱是一個二進位制板條箱。公共板條箱儲存在一個名為 crates.io 的中央儲存庫中 https://crates.io/。
步驟 2 - 包含對外部庫的引用
此示例需要生成一個隨機數。由於內部標準庫不提供隨機數生成邏輯,因此我們需要檢視外部庫或板條箱。讓我們使用 rand 板條箱,它在 crates.io 網站上可用 crates.io
rand 是一個用於隨機數生成的 Rust 庫。Rand 提供了生成隨機數、將其轉換為有用的型別和分佈以及一些與隨機性相關的演算法的實用程式。
下圖顯示了 crates.io 網站和 rand 板條箱的搜尋結果。
將 rand 板條箱的版本複製到 Cargo.toml 檔案中 rand = "0.5.5"。
[package] name = "guess-game-app" version = "0.1.0" authors = ["Mohtashim"] [dependencies] rand = "0.5.5"
步驟 3:編譯專案
導航到專案資料夾。在終端視窗上執行命令 cargo build -
Updating registry `https://github.com/rust-lang/crates.io-index` Downloading rand v0.5.5 Downloading rand_core v0.2.2 Downloading winapi v0.3.6 Downloading rand_core v0.3.0 Compiling winapi v0.3.6 Compiling rand_core v0.3.0 Compiling rand_core v0.2.2 Compiling rand v0.5.5 Compiling guess-game-app v0.1.0 (file:///E:/RustWorks/RustRepo/Code_Snippets/cargo-projects/guess-game-app) Finished dev [unoptimized + debuginfo] target(s) in 1m 07s
rand 板條箱和所有傳遞依賴項(rand 的內部依賴項)將自動下載。
步驟 4 - 理解業務邏輯
現在讓我們看看猜數字遊戲的業務邏輯是如何工作的 -
遊戲最初生成一個隨機數。
提示使用者輸入並猜測數字。
如果數字小於生成的數字,則列印訊息“太低”。
如果數字大於生成的數字,則列印訊息“太高”。
如果使用者輸入程式生成的數字,則遊戲退出。
步驟 5 - 編輯 main.rs 檔案
將業務邏輯新增到 main.rs 檔案中。
use std::io;
extern crate rand;
//importing external crate
use rand::random;
fn get_guess() -> u8 {
loop {
println!("Input guess") ;
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("could not read from stdin");
match guess.trim().parse::<u8>(){ //remember to trim input to avoid enter spaces
Ok(v) => return v,
Err(e) => println!("could not understand input {}",e)
}
}
}
fn handle_guess(guess:u8,correct:u8)-> bool {
if guess < correct {
println!("Too low");
false
} else if guess> correct {
println!("Too high");
false
} else {
println!("You go it ..");
true
}
}
fn main() {
println!("Welcome to no guessing game");
let correct:u8 = random();
println!("correct value is {}",correct);
loop {
let guess = get_guess();
if handle_guess(guess,correct){
break;
}
}
}
步驟 6 - 編譯並執行專案
在終端上執行命令 cargo run。確保終端指向專案目錄。
Welcome to no guessing game correct value is 97 Input guess 20 Too low Input guess 100 Too high Input guess 97 You got it ..
Rust - 迭代器和閉包
在本章中,我們將學習迭代器和閉包在 RUST 中是如何工作的。
迭代器
迭代器有助於迭代一系列值,例如陣列、向量、對映等。迭代器實現了在 Rust 標準庫中定義的 Iterator 特性。iter() 方法返回集合的迭代器物件。迭代器物件中的值稱為專案。迭代器的 next() 方法可用於遍歷專案。當 next() 方法到達集合的末尾時,它返回一個 None 值。
以下示例使用迭代器從陣列中讀取值。
fn main() {
//declare an array
let a = [10,20,30];
let mut iter = a.iter();
// fetch an iterator object for the array
println!("{:?}",iter);
//fetch individual values from the iterator object
println!("{:?}",iter.next());
println!("{:?}",iter.next());
println!("{:?}",iter.next());
println!("{:?}",iter.next());
}
輸出
Iter([10, 20, 30]) Some(10) Some(20) Some(30) None
如果像陣列或向量這樣的集合實現了 Iterator 特性,則可以使用 for...in 語法遍歷它,如下所示 -
fn main() {
let a = [10,20,30];
let iter = a.iter();
for data in iter{
print!("{}\t",data);
}
}
輸出
10 20 30
以下 3 種方法從集合中返回一個迭代器物件,其中 T 表示集合中的元素。
| 序號 | 方法和描述 |
|---|---|
| 1 | iter() iter() |
| 2 | 給出 &T(對 T 的引用)的迭代器 into_iter() |
| 3 | 給出 T 的迭代器 iter_mut() |
給出 &mut T 的迭代器
插圖:iter()
fn main() {
let names = vec!["Kannan", "Mohtashim", "Kiran"];
for name in names.iter() {
match name {
&"Mohtashim" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
println!("{:?}",names);
// reusing the collection after iteration
}
輸出
Hello Kannan There is a rustacean among us! Hello Kiran ["Kannan", "Mohtashim", "Kiran"]
iter() 函式使用借用概念。它返回對集合中每個元素的引用,使集合保持不變並可以在迴圈後重復使用。
插圖 - into_iter()
fn main(){
let names = vec!["Kannan", "Mohtashim", "Kiran"];
for name in names.into_iter() {
match name {
"Mohtashim" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
// cannot reuse the collection after iteration
//println!("{:?}",names);
//Error:Cannot access after ownership move
}
輸出
Hello Kannan There is a rustacean among us! Hello Kiran
此函式使用所有權的概念。它將集合中的值移動到 iter 物件中,即集合被消耗並且不再可用。
插圖 - for 和 iter_mut()
fn main() {
let mut names = vec!["Kannan", "Mohtashim", "Kiran"];
for name in names.iter_mut() {
match name {
&mut "Mohtashim" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
println!("{:?}",names);
//// reusing the collection after iteration
}
輸出
Hello Kannan There is a rustacean among us! Hello Kiran ["Kannan", "Mohtashim", "Kiran"]
此函式類似於 iter() 函式。但是,此函式可以修改集合中的元素。
閉包
閉包指的是一個函式在另一個函式內部。這些是匿名函式——沒有名稱的函式。閉包可以用於將函式分配給變數。這允許程式將函式作為引數傳遞給其他函式。閉包也稱為行內函數。外層函式中的變數可以被行內函數訪問。
語法:定義閉包
let closure_function = |parameter| {
//logic
}
閉包定義可以選擇具有引數。引數括在兩個豎線之間。
closure_function(parameter); //invoking
說明
呼叫閉包的語法實現了 Fn 特性。因此,可以使用 () 語法呼叫它。
fn main(){
let is_even = |x| {
x%2==0
};
let no = 13;
println!("{} is even ? {}",no,is_even(no));
}
輸出
13 is even ? false
說明
fn main(){
let val = 10;
// declared outside
let closure2 = |x| {
x + val //inner function accessing outer fn variable
};
println!("{}",closure2(2));
}
以下示例在函式 main() 中定義了一個閉包 is_even。如果數字為偶數,則閉包返回 true,如果數字為奇數,則返回 false。
輸出
12
Rust - 智慧指標
main() 函式聲明瞭一個變數 val 和一個閉包。閉包訪問在外層函式 main() 中宣告的變數。
| 序號 | Rust 預設情況下在堆疊上分配所有內容。您可以透過將內容包裝在智慧指標(如 Box)中來將其儲存在堆上。像 Vec 和 String 這樣的型別隱式地幫助堆分配。智慧指標實現了下表中列出的特性。這些智慧指標的特性將它們與普通的結構區分開來 - | 特性名稱 |
|---|---|---|
| 1 | 包和描述 | Deref std::ops::Deref |
| 2 | 用於不可變的解引用操作,如 *v。 | Drop std::ops::Drop |
用於在值超出範圍時執行一些程式碼。這有時稱為解構函式
在本章中,我們將學習有關 Box 智慧指標的知識。我們還將學習如何建立像 Box 這樣的自定義智慧指標。
Box
Box 智慧指標也稱為 box,允許您將資料儲存在堆上而不是堆疊上。堆疊包含指向堆資料的指標。除了將資料儲存在堆上之外,Box 沒有效能開銷。
fn main() {
let var_i32 = 5;
//stack
let b = Box::new(var_i32);
//heap
println!("b = {}", b);
}
輸出
b = 5
讓我們看看如何使用 box 將 i32 值儲存在堆上。
fn main() {
let x = 5;
//value type variable
let y = Box::new(x);
//y points to a new value 5 in the heap
println!("{}",5==x);
println!("{}",5==*y);
//dereferencing y
}
為了訪問變數指向的值,請使用解引用。* 用作解引用運算子。讓我們看看如何在 Box 中使用解引用。
輸出
true true
變數 x 是一個值為 5 的值型別。因此,表示式 5==x 將返回 true。變數 y 指向堆。要訪問堆中的值,我們需要使用 *y 進行解引用。*y 返回值 5。因此,表示式 5==*y 返回 true。
插圖 - Deref 特性
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
// Generic structure with static method new
fn new(x:T)-> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0 //returns data
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
// calling static method
println!("5==x is {}",5==x);
println!("5==*y is {}",5==*y);
// dereferencing y
println!("x==*y is {}",x==*y);
//dereferencing y
}
輸出
5==x is true 5==*y is true x==*y is true
標準庫提供的 Deref 特性要求我們實現一個名為 deref 的方法,該方法借用 self 並返回對內部資料的引用。以下示例建立一個結構 MyBox,它是一個泛型型別。它實現了 Deref 特性。此特性幫助我們使用 *y 訪問 y 包裝的堆值。
插圖 - Drop 特性
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x:T)->MyBox<T>{
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -< &T {
&self.0
}
}
impl<T> Drop for MyBox<T>{
fn drop(&mut self){
println!("dropping MyBox object from memory ");
}
}
fn main() {
let x = 50;
MyBox::new(x);
MyBox::new("Hello");
}
Drop 特性包含 drop() 方法。當實現了此特性的結構超出範圍時,將呼叫此方法。在某些語言中,程式設計師必須在每次完成使用智慧指標例項後都呼叫程式碼來釋放記憶體或資源。在 Rust 中,您可以使用 Drop 特性實現自動記憶體釋放。
dropping MyBox object from memory dropping MyBox object from memory
Rust - 併發
在上面的示例中,drop 方法將被呼叫兩次,因為我們在堆中建立了兩個物件。
在併發程式設計中,程式的不同部分獨立執行。另一方面,在並行程式設計中,程式的不同部分同時執行。這兩種模型都同等重要,因為越來越多的計算機利用了它們的多處理器功能。
執行緒
我們可以使用執行緒同時執行程式碼。在當前的作業系統中,已執行程式的程式碼在一個程序中執行,作業系統同時管理多個程序。在您的程式中,您還可以擁有同時執行的獨立部分。執行這些獨立部分的功能稱為執行緒。
建立執行緒
//import the necessary modules
use std::thread;
use std::time::Duration;
fn main() {
//create a new thread
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
//code executed by the main thread
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
輸出
hi number 1 from the main thread! hi number 1 from the spawned thread! hi number 2 from the main thread! hi number 2 from the spawned thread! hi number 3 from the main thread! hi number 3 from the spawned thread! hi number 4 from the spawned thread! hi number 4 from the main thread!
thread::spawn 函式用於建立一個新執行緒。spawn 函式將閉包作為引數。閉包定義了執行緒應執行的程式碼。以下示例從主執行緒列印一些文字,從新執行緒列印其他文字。
主執行緒列印從 1 到 4 的值。
注意 - 當主執行緒結束時,新執行緒將停止。此程式的輸出每次可能略有不同。
thread::sleep 函式強制執行緒停止執行一小段時間,從而允許其他執行緒執行。執行緒可能會輪流執行,但這並非保證——它取決於作業系統如何排程執行緒。在此執行中,首先列印主執行緒,即使來自已生成執行緒的列印語句在程式碼中首先出現。此外,即使已生成執行緒被程式設計為列印直到 9 的值,它在主執行緒關閉之前只到達了 5。
連線控制代碼
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
輸出
hi number 1 from the main thread! hi number 1 from the spawned thread! hi number 2 from the spawned thread! hi number 2 from the main thread! hi number 3 from the spawned thread! hi number 3 from the main thread! hi number 4 from the main thread! hi number 4 from the spawned thread! hi number 5 from the spawned thread! hi number 6 from the spawned thread! hi number 7 from the spawned thread! hi number 8 from the spawned thread! hi number 9 from the spawned thread!
已生成的執行緒可能沒有機會執行或完全執行。這是因為主執行緒很快完成。函式 spawn<F, T>(f: F) -> JoinHandlelt;T> 返回一個 JoinHandle。JoinHandle 上的 join() 方法等待關聯的執行緒完成。
主執行緒和已生成的執行緒繼續切換。