Rust極簡教程

Naylor發表於2022-03-29

簡介

Rust是一門賦予每個人構建可靠且高效軟體能力的程式語言。可靠主要體現在安全性上。其高效不僅限於開發效率,它的執行效率也是令人稱讚的,是一種少有的兼顧開發效率和執行效率的語言。Rust 語言由 Mozilla 開發,最早釋出於 2014 年 9 月。Rust 的編譯器是在 MIT License 和 Apache License 2.0 雙重協議宣告下的免費開源軟體。

特性

  • 高效能:Rust 速度驚人且記憶體利用率極高。由於沒有執行時和垃圾回收,它能夠勝任對效能要求特別高的服務,可以在嵌入式裝置上執行,還能輕鬆和其他語言整合。

  • 可靠性:Rust 豐富的型別系統和所有權模型保證了記憶體安全和執行緒安全,讓您在編譯期就能夠消除各種各樣的錯誤。

  • 生產力:Rust 擁有出色的文件、友好的編譯器和清晰的錯誤提示資訊, 還整合了一流的工具——包管理器和構建工具, 智慧地自動補全和型別檢驗的多編輯器支援, 以及自動格式化程式碼等等。

  • Rustacean:使用 rust 的攻城獅不叫 ruster 而是叫 Rustacean ,我們也不知道為什麼,書上就是這麼說的。

特徵

  • 作為一門程式語言,rust既可以分類為程式導向程式語言,也可以分類為物件導向程式語言
  • rust擁有精細化的基礎資料結構
  • rust中支援泛型,並且擁有泛型列舉
  • rust中支援介面,甚至支援介面預設方法,並且介面既可以作為方法入參也可以作為方法出參

用途

  • 傳統命令列程式
  • 嵌入式
  • 網路服務
  • WebAssembly
  • Web服務
  • ......

安裝

以 windows 11 為例

下載 rustup-init.exe ,雙擊此可執行程式會開啟一個命令列程式,此程式引導安裝,具體安裝過程:

Rust Visual C++ prerequisites

Rust requires the Microsoft C++ build tools for Visual Studio 2013 or
later, but they don't seem to be installed.

The easiest way to acquire the build tools is by installing Microsoft
Visual C++ Build Tools 2019 which provides just the Visual C++ build
tools:

  https://visualstudio.microsoft.com/visual-cpp-build-tools/

Please ensure the Windows 10 SDK and the English language pack components
are included when installing the Visual C++ Build Tools.

Alternately, you can install Visual Studio 2019, Visual Studio 2017,
Visual Studio 2015, or Visual Studio 2013 and during install select
the "C++ tools":

  https://visualstudio.microsoft.com/downloads/

Install the C++ build tools before proceeding.

If you will be targeting the GNU ABI or otherwise know what you are
doing then it is fine to continue installation without the build
tools, but otherwise, install the C++ build tools before proceeding.

Continue? (y/N) y


Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  C:\Users\cml\.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory located at:

  C:\Users\cml\.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  C:\Users\cml\.cargo\bin

This path will then be added to your PATH environment variable by
modifying the HKEY_CURRENT_USER/Environment/PATH registry key.

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-pc-windows-msvc
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>

info: profile set to 'default'
info: default host triple is x86_64-pc-windows-msvc
info: syncing channel updates for 'stable-x86_64-pc-windows-msvc'
info: latest update on 2022-01-20, rust version 1.58.1 (db9d1b20b 2022-01-20)
info: downloading component 'cargo'
  3.8 MiB /   3.8 MiB (100 %)   1.7 MiB/s in  2s ETA:  0s
info: downloading component 'clippy'
  1.6 MiB /   1.6 MiB (100 %)   1.5 MiB/s in  1s ETA:  0s
info: downloading component 'rust-docs'
 18.8 MiB /  18.8 MiB (100 %)   3.3 MiB/s in  5s ETA:  0s
info: downloading component 'rust-std'
 22.9 MiB /  22.9 MiB (100 %)   3.2 MiB/s in  7s ETA:  0s
info: downloading component 'rustc'
 65.2 MiB /  65.2 MiB (100 %) 493.2 KiB/s in  1m 14s ETA:  0s
info: downloading component 'rustfmt'
  2.2 MiB /   2.2 MiB (100 %) 631.2 KiB/s in  3s ETA:  0s
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 18.8 MiB /  18.8 MiB (100 %)   1.9 MiB/s in  6s ETA:  0s
info: installing component 'rust-std'
 22.9 MiB /  22.9 MiB (100 %)  10.5 MiB/s in  2s ETA:  0s
info: installing component 'rustc'
 65.2 MiB /  65.2 MiB (100 %)  12.2 MiB/s in  5s ETA:  0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-pc-windows-msvc'

  stable-x86_64-pc-windows-msvc installed - rustc 1.58.1 (db9d1b20b 2022-01-20)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload its PATH environment variable to include
Cargo's bin directory (%USERPROFILE%\.cargo\bin).

Press the Enter key to continue.

核心元件

  • rustup:安裝、更新rust用的,rustup doc 可檢視安裝包中的官方指導文件
  • cargo:包管理器和編譯原始碼工具
  • rustc:將rust原始檔編譯成可執行程式
  • rustdoc:為rust專案生成說明文件

常用命令

命令 說明 備註
rustup doc 開啟官方指導文件
cargo new projectName 建立一個rust工程 示例:cargo new firstRustProject
cargo run 執行rust工程
cargo build 編譯rust工程 若增加了依賴,即修改了toml檔案,需要重新編譯

基礎語法

  • fn 宣告函式
  • let 宣告變數,變數預設不可變,加 mut 的變數為可變變數
  • 雖然所有變數宣告都用let關鍵字,但是rust是強型別的語言,內建了強大的型別推導
  • 函式引數:引數名:引數資料型別
  • 變數重影性質:同一個名稱可以被多個變數使用,後面的變數會覆蓋前面的變數
  • const 宣告常量
  • //註釋一行,/**/註釋多行
  • 使用英文分號換行

示例:


fn main() {
    println!("Hello, world!");
       //變數預設是不可變的,加上 mut 關鍵字就可以重新賦值。
       let mut  x=5;
       println!("The value of x is   {}  ",x);
   
       x=6;
       println!("The value of x is   {}  ",x);
   
   
       //變數的隱藏
       let  money=100;
       println!("money is {}",money);
       let money =money+8;
       println!("money is {}",money);
       let money="一百元";
       println!("money is {}",money);
   
       //常量使用 const 關鍵字宣告,宣告的時候必須指定資料型別,常量名全大寫。
       //不需要let , 不可使用mut 修飾
       const MAX_PIONTS: u32=888;
       println!("The constant is {}",MAX_PIONTS);
       
    let  result:char= a_function(88, 'M', false);
    println!("result is {}",result);
}
fn a_function(a:u64,b:char,c:bool)-> char{
    println!("a is {}",a);
    println!("b is {}",b);
    println!("c is {}",c);
    return  'N';
}


輸出:

Hello, world!
The value of x is   5
The value of x is   6
money is 100
money is 108
money is 一百元
The constant is 888
a is 88
b is M
c is false
result is N

資料型別

標量型別

整數,浮點,布林,字元

  • 整數:i8,u8,i16,u16,i32,u32,i64,u64,i128,u128,isize,usize
    • i表示有符號
    • u表示無符號
    • isize 和 usize 兩種整數型別是用來衡量資料大小的,它們的位長度取決於所執行的目標平臺,如果是 32 位架構的處理器將使用 32 位位長度整型。
  • 浮點:f32,f64

複合型別

可以將多個值放到一個資料型別中。

  • 元組-tuple。長度固定,元素的資料型別可以不同
  • 陣列,長度固定,元素的資料型別必須相同
  • Vector:不是標準庫提供的。和陣列類似,長度可變

示例


fn main() {
    println!("Hello, world!");
    let q=3.0;
    let q:f32=5.00;
    let w=true;
    let r:bool =false;
 
    let t='?';
    let tup :(i32,u64,bool) =(88,99,false);
    println!("元素1:{},元素2:{},元素3:{}",tup.0 , tup.1, tup.2);
    let arr:[u64;5]=[1,2,3,5,5];
    let arr2=['E';9];
    println!("arr piont 2  is :{}",arr[1]);
    println!("arr2 piont 2  is :{}",arr2[1]);
    //Vector:todo
}


條件語句

和大多數程式語言一樣, if - else if - else 表示條件語句,在 if 後面不用加括號。


fn main() {
    println!("Hello, world!");
    //條件語句
    let a = 12;
    let b;
    if a > 0 {
        b = 1;
    } else if a < 0 {
        b = -1;
    } else {
        b = 0;
    }
    println!("b is {}", b);
    //三元運算子
    let x = 3;
    let number = if x > 0 { 1 } else { -1 };
    println!("number 為 {}", number);
}


迴圈

  • while
  • for
  • loop:終止迴圈,並返回一個值

fn main() {
    println!("Hello, world!");
    //迴圈
    //while
    let mut number = 1;
    while number != 4 {
        println!("{}", number);
        number += 1;
    }
    println!("while cycle  EXIT");
    //for - 迭代器
    let a = [10, 20, 30, 40, 50];
    for i in a.iter() {
        println!("元素值為 : {}", i);
    }
    println!("for-iter cycle  EXIT");
    //for - 下標
    let b = [10, 20, 30, 40, 50];
    let mut length = b.len();
    println!("b 陣列的長度是:{}", length);
    for i in 0..length {
        println!("b[{}] = {}", i, b[i]);
    }
    println!("for-index cycle  EXIT");
    //loop 終止迴圈,並返回一個值
    let s = ['R', 'U', 'N',  'O', 'B'];
    let mut i = 0;
    let location = loop {
        let ch = s[i];
        if ch == 'B' {
            break i;
        }
        i += 1;
    };
    println!(" \'B\' 的索引為 {}", location);
}


輸出&輸入

輸出


println!("print a  small =_=*");

輸出花括號

花括號中套花括號就可以輸出花括號
示例程式碼:


 let mut name = String::from("cml");
    println!("輸出中帶花括號:{{  {}  }}", name);


以上程式碼輸出:

輸出中帶花括號:{  cml  }

輸出非基礎型別

  println!("輸出一個結構體,a={:?}", a);

輸入


 let mut guess = String::new();
 io::stdin().read_line(&mut guess).expect("無法讀取行");

所有權

所有權可以理解為名稱空間+作用域+指標。

  • 基本資料型別(值型別)變數在棧空間中可以複製。先給x賦值9(let x = 9),將x賦值給y等同於直接給y賦值9(let y = x 等同於let y = 9)

  • 引用型別變數在堆空間中的“值引用”可以複製,但是儲存在棧空間的“值”不可複製。因此,引用型別變數僅可被消費一次

  • 引用型別變數可以通過“克隆”的方式複製

  • 特殊的資料型別--->型別的引用(即指標),使用 & 關鍵字表示

    • 指標存放在棧中,便於理解可以將指標看作“特殊的值型別”
    • 被借用的不可變變數,不可再借用給其他人
    • 指標型別的不可變變數,不可被修改
    • 值型別(基本資料型別)變數也可以使用指標,但是一般不建議這樣使用
  • 可變變數也可以有指標

  • rust中不允許出現空指標

示例程式碼:


fn main() {
    println!("Hello, world!");
    //01.基本資料型別(值型別)變數在棧空間中可以複製。先給x賦值9(let x = 9),將x賦值給y等同於直接給y賦值9(let y = x 等同於let y = 9)
    let x = 9;
    let y = x;
    //x is :9 , y is :9
    println!("x is :{0} , y is :{1}", x, y);
    let c1 = 'M';
    let c2 = c1;
    //c1 is :M , c2 is :M
    println!("c1 is :{0} , c2 is :{1}", c1, c2);
    //02.引用型別變數在堆空間中的“值引用”可以複製,但是儲存在棧空間的“值”不可複製。因此,引用型別變數僅可被消費一次。
    let s1 = String::from("hello");
    let s2 = s1;
    //編譯錯誤:use of moved value: `s1`
    //let s3 = s1;
    //編譯錯誤:borrow of moved value: `s1`
    //println!("s1 is :{0} , s2 is :{1}", s1, s2);
    //03.引用型別變數可以通過“克隆”的方式複製。
    let h1 = String::from("hello");
    let h2 = h1.clone();
    let h3 = h1.clone();
    //h1 = hello, h2 = hello , h3 is :hello
    println!("h1 = {}, h2 = {} , h3 is :{}", h1, h2, h3);
    //04.特殊的資料型別--->型別的引用(即指標),使用 & 關鍵字表示
    let k1 = String::from("hello");
    //k1為String型別:std::String::String ; k2 為帶String型別的指標型別:&std::String:String
    let k2 = &k1;
    let k3 = k2;
    //指標存放在棧中,便於理解可以將指標看作“特殊的值型別”。所以雖然k2已經賦值給了k3,任然可以賦值給k4。
    let k4 = k2;
    //k1無法賦值給k5,因為k1已經被借用了。這是出於安全考慮,試想如果多個人都可以借用k1,意味著多個人可以修改k1,那勢必對正在使用它的人正在進行的工作產生影響
    // let k5 = k1;
    //編譯錯誤:`k2` is a `&` reference, so the data it refers to cannot be borrowed as mutable
    //既然k2是指標型別,那麼就不允許被修改,因為k2的“值”本身是借來的,如果修改了,那麼勢必對正在使用它的人正在進行的工作產生影響
    //k2.push_str("world");
    println!("k1 is {}, k2 is {}, k3 is :{} , k4 is : {}", k1, k2, k3, k4);
    //值型別(基本資料型別)變數也可以使用指標,但是一般不建議這樣使用
    //n1為i32型別;n2為&i32型別
    let n1 = 8;
    let n2 = &n1;
    println!("n1 is :{} , n2 is :{}", n1, n2);
    //05.可變變數的指標。
    let mut m1 = String::from("run");
    let m2 = &mut m1;
    m2.push_str(",world");
    println!("m2 is :{}", m2);
    //編譯錯誤:cannot borrow `m1` as immutable because it is also borrowed as mutable
    //借出去後,所有權已不在擁有,所以無法被消費
    // println!("m1 is :{} , m2 is :{}", m1, m2);
    //編譯錯誤:cannot borrow `m2` as mutable, as it is not declared as mutable。cannot borrow as mutable
    //不可將借來的東西再借給別人
    // let m3 = &mut m2;
    //06.rust中不允許出現空指標
}


以上程式碼輸出:

Hello, world!
x is :9 , y is :9
c1 is :M , c2 is :M
h1 = hello, h2 = hello , h3 is :hello
k1 is hello, k2 is hello, k3 is :hello , k4 is : hello
n1 is :8 , n2 is :8
m2 is :run,world

切片

切片是指向資料結構(字串、集合)一部分內容的引用。

不願意將rust中的切片理解為一種“型別”,實際上也不是;更不願將rust中的切片理解為一種“集合”。暫且將切片理解成一種物件吧。例如 &s[0..5] 就是獲取到了字串 s 索引從0到5位置的元素,包含0不包含5。

rust中的切片部分主要是要理解索引和下標的概念。

示例程式碼:


fn main() {
    println!("Hello, world!");
    let s = String::from("hello,world.");
    //ss 的資料型別為:&str
    let ss = &s[0..5];
    println!("s is : {} , ss is : {}", s, ss);
    let arr = [1, 3, 5, 7, 9];
    //start_part 的資料型別為:&[i32]
    let start_part = &arr[0..3];
    let end_part = &arr[3..];
    let full_part = &arr[..];
    println!(
        "arr is : {:?} , start_part  is : {:?} , end_part is : {:?} , full_part is : {:?}",
        arr, start_part, end_part, full_part
    );
}


以上程式碼輸出:

Hello, world!
s is : hello,world. , ss is : hello
arr is : [1, 3, 5, 7, 9] , start_part  is : [1, 3, 5] , end_part is : [7, 9] , full_part is : [1, 3, 5, 7, 9]

結構體

struct 類似 java 中的類,用來自定義一種資料結構,這種資料結構一般用來描述生活中的某一個物件。struct 中可以包含屬性和方法。使用結構體分為兩步:首先需要定義一個結構體,然後需要例項化一個結構體,再然後才可以使用。

一旦結構體例項化的時候是可變的,即使用 mut 修飾, 那麼結構體中所有的屬性都將是可變的。

結構體屬性:

  • 宣告方式,屬性名:資料型別;
  • 即使是最後一個屬性,末尾也要加英文逗號;
  • 屬性可以是另外一個結構體,但不能是本身;
  • 屬性可以是一個元組
  • 屬性可以是一個列舉

示例程式碼:


/**
 * 人
 */
#[derive(Debug)]
struct Person {
    //結構體屬性:宣告方式,屬性名:資料型別;即使是最後一個屬性,末尾也要加英文逗號;屬性可以是另外一個結構體,但不能是本身;屬性可以是一個元組
    active: bool,
    name: String,
    email: String,
    sign_in_count: u64,
    nation: Nation,
    parent: Parent,
    empty: Empty,
    tuple: (u32, u32),
}
/**
 * 國家結構體
 */
#[derive(Debug)]
struct Nation {
    name: String,
    area: String,
    time_zone: u64,
}
/**
 * 父母結構體
 * 特殊的結構體,元組結構體。
 */
#[derive(Debug)]
struct Parent(String, String);
/**
 * 空結構體
 */
#[derive(Debug)]
struct Empty;
fn main() {
    println!("Hello, world!");
    //例項化一個結構體
    let p1 = Person {
        active: true,
        name: String::from("cml"),
        email: String::from("cnaylor@163.com"),
        sign_in_count: 99,
        nation: Nation {
            name: String::from("xm"),
            area: String::from("north"),
            time_zone: 8,
        },
        parent: Parent(String::from("baba"), String::from("mama")),
        empty: Empty,
        tuple: (8, 8),
    };
    //例項化一個可變結構體
    let mut p2 = Person {
        active: true,
        name: String::from("cml"),
        email: String::from("cnaylor@163.com"),
        sign_in_count: 99,
        nation: Nation {
            name: String::from("xm"),
            area: String::from("north"),
            time_zone: 8,
        },
        parent: Parent(String::from("baba"), String::from("mama")),
        empty: Empty,
        tuple: (8, 8),
    };
    //給結構體例項重新賦值
    p2.email = String::from("c@163.com");
    let email = &p2.email;
    println!("p1 is : {:#?} , p2 is : {:#?} , email is :{}", p1, p2, email);
    #[derive(Debug)]
    struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64,
    }
    fn build_user(email: String, username: String) -> User {
        let u = User {
            email: email,
            //簡寫。函式引數和結構體屬性名相同,可簡寫
            // email,
            username,
            active: true,
            sign_in_count: 1,
        };
        //從已建立的結構體例項建立例項
        // let u2 = User { ..u };
        let u3 = User {
            email: String::from("o@163.com"),
            ..u
        };
        return u3;
    }
    let u = build_user(String::from("a@163.com"), String::from("tom"));
    println!("u is : {:?}", u);
}

以上程式碼輸出:

Hello, world!
p1 is : Person {
    active: true,
    name: "cml",
    email: "cnaylor@163.com",
    sign_in_count: 99,
    nation: Nation {
        time_zone: 8,
    },
    parent: Parent(
        "baba",
        "mama",
    ),
    empty: Empty,
    tuple: (
        8,
        8,
    ),
} , email is :c@163.com
u is : User { active: true, username: "tom", email: "o@163.com", sign_in_count: 1 }

列舉

列舉表示某一個物件可能的值,實際使用中常用來表達:ip地址型別,訂單狀態,人員性別,實名認證方式等某一事物簡短又不經常變化的“可選值”。

列舉成員:

  • 英文逗號分割
  • 可以是字串,數字型別或者結構體。甚至可以包含另一個列舉
  • 要合理使用標準庫中的列舉,避免重複定義
  • 可以使用match比對列舉成員,類似java中的switch-case
  • 可以用 if - let ---> else 比對列舉成員

Match

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("File opened successfully.");
        },
        Err(err) => {
            println!("Failed to open the file.");
        }
    }
}

If-let

fn main() {
    enum Book {
        Papery(u32),
        Electronic(String)
    }
    let book = Book::Electronic(String::from("url"));
    if let Book::Papery(index) = book {
        println!("Papery {}", index);
    } else {
        println!("Not papery book");
    }
}

Option

Option 是一個標準庫中的列舉,用來處理空值(null) 的情況。Option是一個泛型列舉,接受型別 T 。Option 要乾的事情和java 中的 optional(java8新特性) 類似。

簡言之:

  • 我們在設計資源提供者時候,如果不確定資源提供者是否會輸出null,那麼我們可以將返回值定義為 Option。若一切正常,資源消費者直接消費返回值T就行;若出了問題,資源消費者得到的返回值將為Option.None 。
  • 同理,作為呼叫方,我們可以定義一個Option的變數來接收資源提供者的輸出值,而後根據返回值是否為none處理業務邏輯。

Option 原始碼:

enum Option<T> {
    Some(T),
    None,}

使用舉例:


// 整數除法。
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // 失敗表示成 `None` 取值
        None
    } else {
        // 結果 Result 被包裝到 `Some` 取值中
        Some(dividend / divisor)
    }
}
// 此函式處理可能失敗的除法
fn try_division(dividend: i32, divisor: i32) {
    // `Option` 值可以進行模式匹配,就和其他列舉型別一樣
    let result = checked_division(dividend, divisor);
    if result!=Option::None
    {
        //獲取返回值
        println!("Nice, result is :{:?}", result.unwrap());
    }
 
    match checked_division(dividend, divisor) {
        None => println!("{} / {} failed!", dividend, divisor),
        Some(quotient) => {
            println!("{} / {} = {}", dividend, divisor, quotient)
        }
    }
}
fn main() {
    try_division(4, 2);
    try_division(1, 0);
}


以上程式碼輸出:

Nice, result is :2
4 / 2 = 2
1 / 0 failed!

集合

Rust 標準庫中包含一系列被稱為 集合(collections)的非常有用的資料結構。大部分其他資料型別都代表一個特定的值,不過集合可以包含多個值。不同於內建的陣列和元組型別,集合指向的資料是儲存在堆上的,這意味著資料的數量不必在編譯時就已知,並且還可以隨著程式的執行增長或縮小。

vector容器

vector 允許我們在一個單獨的資料結構中儲存多個值,所有值在記憶體中彼此相鄰排列。vector 只能儲存相同型別的值。

如果藉助列舉,有時候 vector 也可以變相儲存不同型別的值。

示例程式碼:


fn main() {
    println!("Hello, world!");
    //新建一個 vector
    let mut v: Vec<i32> = Vec::new();
    let v2 = vec![1, 2, 3];
    println!("v is : {:?} , v2 is : {:?}", v, v2);
    //更新
    v.push(888);
    v.push(111);
    v.push(222);
    v.push(333);
    //刪除
    v.remove(0);
    //查詢
    let two = v[1];
    let two2 = &v[1];
    //get方法返回的是Option
    let three = v.get(2);
    println!("two is : {} , three is  : {:?}", two, three);
    //遍歷
    let arr = vec![100, 32, 57];
    for i in &arr {
        println!("arr item for --> {}", i);
    }
    //藉助列舉,vector中可以儲存不同的資料型別
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
    let  s = &row[1];
    println!("row is : {:?} , s is : {:?} ", row, s);
}
#[derive(Debug)]
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}


以上程式碼輸出:

Hello, world!
v is : [] , v2 is : [1, 2, 3]
two is : 222 , three is  : Some(333)
arr item for --> 100
arr item for --> 32
arr item for --> 57
row is : [Int(3), Text("blue"), Float(10.12)] , s is : Text("blue")

String

你沒有看錯,rust 中,字串也是集合,是什麼集合呢? 是“字元”的集合。注意:上面提到過字元是 rust 的基礎標量型別,但是字串不是標量型別,而是集合型別。

  • 字串是UTF-8編碼,中文不會亂碼,一個英文字元佔1位元組,一個漢字佔2位元組。
  • 字串底層實際是一個結構體,資料儲存在結構體中的 Vec (vector容器)裡面 。
  • rust中不建議用下標訪問字串元素:rust中的字串大部分時候和 java 中的不太一樣,相比較而言更低階(對部分使用者來說),但是rust中的字串更能真實的反應文字本來的樣子。

更新字串的方式:

  • push_str
  • push
  • 加號
  • format!

示例程式碼:


fn main() {
    println!("Hello, world!");
    //建立
    let mut s = String::new();
    let data = "initial contents";
    let ss = data.to_string();
    let sss = String::from("你好!");
    println!("s is : {} , ss is  : {} , sss is  : {}", s, ss, sss);
    //更新
    let mut word = String::from("Aa");
    word.push_str("Bb");
    word.push('C');
    word += "cDd";
    word = format!("{}{}-{}", word, "Ee", "Ff");
    println!("final  word is : {} ", word);
    //不要使用下標訪問
    let p = String::from("hello");
    let p1 = &p[0..3];
    let k = String::from("你好我是陳明亮");
    let k1 = &k[0..3];
    //輸出結果可能不是大多數人預期:p1 is : hel , k1 is : 你
    println!("p1 is : {} , k1 is : {}", p1, k1);
    //遍歷
    word+="北京";
    for i in word.chars() {
        println!("word ---> item is :{}", i);
    }
}


以上程式碼輸出:


Hello, world!
s is :  , ss is  : initial contents , sss is  : 你好!
final  word is : AaBbCcDdEe-Ff
p1 is : hel , k1 is : 你
word ---> item is :A
word ---> item is :a
word ---> item is :B
word ---> item is :b
word ---> item is :C
word ---> item is :c
word ---> item is :D
word ---> item is :d
word ---> item is :E
word ---> item is :e
word ---> item is :-
word ---> item is :F
word ---> item is :f
word ---> item is :b
word ---> item is :C
word ---> item is :c
word ---> item is :D
word ---> item is :d
word ---> item is :E
word ---> item is :e
word ---> item is :-
word ---> item is :F
word ---> item is :f
word ---> item is :北
word ---> item is :京

程式碼組織

講道理,rust 中的程式碼組織相比 java 、CSharp 這些所謂高階語言,要複雜的多。大概包含兩個部分:名稱空間和訪問許可權。

名稱空間

rust 中針對包管理主要有三個概念:包(package),箱(crate),模組(module)。三個概念形成一個樹狀結構,包中包含箱,箱中包含模組。

  • 包:cargo new 出來的工程就是一個包,包中包含箱。一個包會包含有一個 Cargo.toml 檔案,闡述如何去構建裡面的 crate
  • 箱:箱是二進位制程式檔案或者庫檔案,箱中包含模組。在 https://crates.io 這個網站中有很多別人開發好的箱,我們可以通過引入這些箱提升我們的開發效率。
  • 模組:基於業務功能的實現和程式碼組織的考量,我們將功能相似或共同完成一個功能的程式碼分到一個組裡面,這個組就是模組了,一個箱中往往包含多個模組。當然,模組中可以包含子模組。

訪問其他mod和crate

訪問許可權和關鍵字

Rust 中預設所有項(函式、方法、結構體、列舉、模組和常量)都是私有的。父模組中的項不能使用子模組中的私有項,但是子模組中的項可以使用他們父模組中的項。

關鍵字:

  • pub:新增 pub 訪問修飾符的項將被公開,所有人可以訪問
  • use:將其他名稱空間引入當前作用域
  • as :使用 as 可將use進來的名稱空間取一個別名,一般用來解決當兩個需 use 物件的名稱相同時候區分他們
  • mod :定義一個 module 或者匯入一個 module。

訪問其他檔案中的物件

  • 一個檔案預設就是 module ,所以 other_rs_file.rs 就是一個名為 other_rs_file 的 module

  • main.rs 中如果要使用 other_rs_file 中的物件需要先匯入這個 module: mod other_rs_file ,使用 mod::物件名 訪問其中物件

  • 如果 other_rs_file.rs 中定義了子 module , main.rs 僅需匯入頂層 module (即 mod other_rs_file) ,使用 mod::子mod::物件名 訪問其中物件

  • rust 預設將rs檔案識別為一個 module 處理, 但是無法將資料夾識別為一個  module ,所以如果我們要訪問entity資料夾中dept.rs檔案中的物件,需要在entiry資料夾中新增一個mod.rs 並在其中定義 dept module ,訪問其中物件的時候同樣僅需匯入頂層 mod ,然後通過 entity::dept::Dept 方式訪問 dept 物件。

工程目錄檔案結構:

│  main.rs
│  other_rs_file.rs
└─entity
        dept.rs
        mod.rs

示例程式碼:
other_rs_file.rs:


pub mod other {
    #[derive(Debug)]
    pub struct User {
        //結構體屬性預設是私有的,若外部需要訪問需新增pub關鍵字
        pub name: String,
        pub age: i32,
    }
    fn init_user(n: String) -> User {
        let u = User {
            name: String::from(n),
            age: 100,
        };
        return u;
    }
    pub fn add_user(n: String, a: i32) -> bool {
        return false;
    }
    //列舉不用給元素新增 pub ,只要列舉是公開的,裡面元素就是公開的
    #[derive(Debug)]
    pub enum IpAddrKind {
        IPV4,
        IPV6,
    }
}


dept.rs:

#[derive(Debug)]
pub struct Dept {
    pub name: String,
    pub no: i32,
}

mod.rs:

pub mod dept;

main.rs:


mod other_rs_file;
mod  entity;
fn main() {
    println!("Hello, world!");
    /*
     * main.rs 同級 rs檔案
     * 引入其他rs檔案,需先匯入module ,通過mod名稱(即檔名稱)::物件名稱 訪問其內部物件
     * 如果在其他檔案內部定義了子 module , 匯入方式不變,訪問方式:mod名稱::子mod名稱::物件名稱
     * 匯入時候只匯入頂層mod
     */
    let u = other_rs_file::other::User {
        name: String::from("cml"),
        age: 9,
    };
    println!("例項化另外一個rs檔案中定義的結構體,u is: {:?}", u);
    /*
    * main.rs 上級資料夾 rs 檔案
    * rust 預設將rs檔案識別為一個 module 處理, 但是無法將資料夾識別為一個  module ,所以如果我們要訪問entity資料夾中dept.rs檔案中的物件,需要在entiry資料夾中新增一個mod.rs 並在其中定義 dept module。
    */
    let d=entity::dept::Dept{
        name:String::from("技術部門"),
        no:5,
    };
   
    println!("例項化另外一個rs檔案中定義的結構體,d is: {:?}", d);
}

main輸出:

Hello, world!
例項化另外一個rs檔案中定義的結構體,u is: User { name: "cml", age: 9 }
例項化另外一個rs檔案中定義的結構體,d is: Dept { name: "技術部門", no: 5 }

使用第三方庫

  • 使用 use 匯入其他的 crate
  • 預設的 crate 源是 crate.io 這個網站
  • 匯入其他 crate 需要在 Cargo.toml 中定義相關 dependencies。標準庫除外,標準庫中的 crate 可以直接匯入使用
  • 使用花括號匯入一個 crate 下多個 module :use std::{self,io,cmp::Ordering}
  • 使用 * 號匯入一個 crate 下所有 module:use std:?;

示例程式碼:
Cargo.toml:

[package]
name = "Crate"
version = "0.1.0"
edition = "2021"


[dependencies]
rand = "0.5.5"

main.rs:


/*
* 匯入標準庫
*/
use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;
// use std::cmp::Ordering
use std::*;
use std::{self, cmp::Ordering, io};
/*
* 匯入外部第三方庫
*/
use rand::{thread_rng, Rng};
fn main() {
    println!("Hello, world!");
    let fmtr = FmtResult::Ok;
    let ior = IoResult::Ok("成功");
    println!("fmtr 無法列印 , ior is : {:?}", ior);
    //生成隨機數
    let mut rng = thread_rng();
    let x: u32 = rng.gen();
    println!("x is :{}", x);
}


異常處理

  • rust中的異常不叫 exception , 而是叫做 panic (恐慌),意思是編譯器執行到這裡的程式碼害怕了,不敢繼續執行了,就恐慌了,相當於程式就終止了。
  • rust官方對於錯誤處理有兩個類別:可恢復錯誤(如開啟檔案失敗算是一個錯誤,但是這種錯誤可以重試);不可恢復錯誤(如索引越界,這就是實打實的bug了,程式將崩潰)
  • 對於不可恢復的錯誤,官方的做法是提供一個Result<T, E> 列舉類來處理,如 std::fs::File--->Result 。若程式執行成功返回 Ok(T) ,即響應結果,若程式執行出現錯誤返回 Err(E) ,即錯誤資訊。

工程結構:

│  Cargo.lock
│  Cargo.toml
│  hello.txt
├─src
│      main.rs

示例程式碼:

use std::fs;
use std::fs::File;
use std::io;
use std::io::Read;

fn main() {
    println!("Hello, world!");

    let mut f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("File opened successfully.");
        }
        Err(err) => {
            //開啟失敗
            println!("Failed to open the file.");
            match err.kind() {
                //開啟失敗原因,不存在情況建立
                io::ErrorKind::NotFound => {
                    println!("File Not Found. soon create . ");
                    File::create("hello.txt");
                    fs::write("hello.txt", "I am  hello.txt");
                }
                _ => {
                    //其他原因直接拋 panic
                    panic!("Failed to open the file.");
                }
            }
        }
    }

    //列印 txt內容
    let mut file = std::fs::File::open("hello.txt").unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    print!("txt 的內容是:{}", contents);
}

以上程式碼輸出:

Hello, world!
Failed to open the file.
File Not Found. soon create .
txt 的內容是:I am  hello.txt

泛型

泛型概念

泛型這種程式語言得設計絕非 rust 獨有的,實際上在C# , Java 這些語言中早就引入了泛型的設計思想。泛型即型別的泛化,在編寫程式碼的時候並不知道具體的資料型別或者資料結構是什麼樣子的,而是定義一個標識,在執行時此標識可動態替換為實際的資料型別。很顯然這種設計能夠讓開發人員避免編寫很多相似的重複的程式碼。

在 rust 中,可以在如下位置編寫泛型程式碼:

  • 泛型函式
  • 泛型結構體
  • 泛型列舉

示例程式碼:


fn main() {
    println!("Hello, world!-->泛型");
    /*
     * 泛型函式
     */
    let a = [2, 4, 6, 3, 1];
    println!("數字陣列中最大元素是 = {}", max(&a));
    let b = ["A", "B", "C"];
    println!("字元陣列中最大元素是 = {}", max(&b));
    /*
     * 泛型結構體
     */
    // i32
    let p1 = Point { x: 1, y: 2 };
    //f64
    let p2 = Point { x: 1.0, y: 2.0 };
    println!("p1  ={:?} , p2 = {:?}", p1, p2);
}
//求最大元素
fn max<T: std::cmp::PartialOrd>(array: &[T]) -> &T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i] > array[max_index] {
            max_index = i;
        }
        i += 1;
    }
    return &array[max_index];
}
//泛型結構體
#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}


以上程式碼輸出:

Hello, world!-->泛型
數字陣列中最大元素是 = 6
字元陣列中最大元素是 = C
p1  =Point { x: 1, y: 2 } , p2 = Point { x: 1.0, y: 2.0 }

特性(介面)

特性(trait)概念接近於 Java 中的介面(Interface),但兩者不完全相同。特性與介面相同的地方在於它們都是一種行為規範,可以用於標識哪些類有哪些方法

一個物件的行為由其可供呼叫的方法構成。如果可以對不同型別呼叫相同的方法的話,這些型別就可以共享相同的行為了。trait 定義是一種將方法簽名組合起來的方法,目的是定義一個實現某些目的所必需的行為的集合。

  • 一個結構體可以同時實現多個介面,但是一個結構體的繼承者類(即 impl , rust 中實現類無自己的類名)僅可實現一個介面
  • trait 介面可以有預設實現方法,這個就和java8一樣,介面可以有預設的已實現方法

示例程式碼:


use std::time::{SystemTime, UNIX_EPOCH};
fn main() {
    println!("Hello, world!--->特性(介面)");
    //例項化Person
    let p = Person {
        name: String::from("cml"),
        age: 100,
    };
    let d = p.describe();
    println!("d = {}", d);
    let s = p.tostring();
    println!("s={}", s);
    let t = p.nowTime();
    println!("t ={:?}", t);
}
//定義一個描述介面
trait Descriptive {
    fn describe(&self) -> String;
    //介面預設方法
    fn nowTime(&self) -> SystemTime {
        let start = SystemTime::now();
        return start;
    }
}
//定義一個 tostring 介面
trait ToString {
    fn tostring(&self) -> String;
}
//定義一個結構體
#[derive(Debug)]
struct Person {
    name: String,
    age: u8,
}
//結構體的一個繼承類實現了 Descriptive 介面
impl Descriptive for Person {
    fn describe(&self) -> String {
        format!(
            "I am   Person , name is : {} , age is : {}",
            self.name, self.age
        )
    }
}
impl ToString for Person {
    fn tostring(&self) -> String {
        format!("Person:{{name:{},age:{}}}", self.name, self.age)
    }
}


以上程式碼輸出:

Hello, world!--->特性(介面)
d = I am   Person , name is : cml , age is : 100
s=Person:{name:cml,age:100}
t =SystemTime { intervals: 132914629768805244 }

檔案和IO

rust 標準庫中的 std::fs 可以用來操作檔案

示例程式碼:


use std::fs;
use std::fs::OpenOptions;
use std::io::prelude::*;
fn main() {
    println!("Hello, world!--->檔案io");
    //讀取檔案內容,一次性讀取
    //檔案內容:This is a text file.
    let text = fs::read_to_string("text.txt").unwrap();
    println!("txt檔案的內容是:{}", text);
    //寫入檔案,追加
    //追加完成檔案內容:
    append();
    println!("追加後,txt檔案的內容是:{}", text);
    //寫入檔案,會覆蓋
    //覆蓋之後檔案內容:
    fs::write("text.txt", "FROM RUST PROGRAM").unwrap();
    println!("覆蓋寫入後,txt檔案的內容是:{}", text);
}
//追加
fn append() -> std::io::Result<()> {
    let mut file = OpenOptions::new().append(true).open("text.txt")?;
    file.write(b" APPEND WORD")?;
    Ok(())
}

以上程式碼輸出:

Hello, world!--->檔案io
txt檔案的內容是:This is a text file.
追加後,txt檔案的內容是:This is a text file.
覆蓋寫入後,txt檔案的內容是:This is a text file.

物件導向

物件導向程式設計(OOP)思想是一個概念,是一種思想指導,圍繞“物件”展開,從萬物皆是物件的角度出發,一個 crate , 一個 module ,一個 struct ,一個列舉等等都是一個個獨立的物件,能自主表達一個事物、某種特徵。而運用封裝、繼承、多型等手段可以讓物件與物件之間產生某種聯絡,進而表達更多的事物,解決更多的問題。

物件導向思想是構建大型應用軟體系統的基石。

rust 語言中 可以通過 結構體,列舉,特性(trait)等來實現oop思想。

併發程式設計

rust 中的併發程式設計主要得益與 執行緒 、 訊息傳遞和互斥鎖。
Rust 語言是滿足多執行緒特性的,所以 rust 可以滿足 主-->子 多工應用場景。

訊息傳遞有點類似與訊息佇列,但訊息佇列一般跨程式或執行緒,而 rust 中的訊息傳遞主要是主子執行緒中資料的傳遞。

執行緒

  • rust 標準庫 std::thread 中的 spawn 函式用來建立一個新的執行緒
  • 通過在主執行緒中 join 子執行緒,讓主執行緒阻塞,確保所有子執行緒執行完畢再繼續執行子執行緒。這種情況在實際開發中非常常見,比如在獲取使用者個人中心資訊介面中,我們將獲取基本資訊、訂單資訊、積分資訊、使用者訊息資訊分別放在4個子執行緒中併發執行,在主執行緒中等待所有子執行緒都響應了再將結果彙總返回給呼叫方。

示例程式碼:


use std::thread;
use std::time::Duration;
fn main() {
    println!("[主執行緒] Hello, world!--->執行緒");
    //建立子執行緒
    for item in 0..5 {
        println!("[主執行緒] 即將建立一個子執行緒,當前迴圈變數:{}", item);
        let child_thread = thread::spawn(|| {
            for i in 0..2 {
                println!("[子執行緒] hi number {} from the spawned thread!", i);
                thread::sleep(Duration::from_millis(1));
            }
        });
    }
    for i in 0..3 {
        println!("[主執行緒] hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
    //執行緒join
    let child1 = thread::spawn(|| {
        println!("[child-1]hi I am spawned thread!");
        thread::sleep(Duration::from_millis(1));
    });
    let child2 = thread::spawn(|| {
        println!("[child-2]hi I am spawned thread!");
        thread::sleep(Duration::from_millis(1));
    });
    child1.join().unwrap();
    child2.join().unwrap();
    println!("[main]hi I am main thread!");
}

以上程式碼輸出:

[主執行緒] Hello, world!--->執行緒
[主執行緒] 即將建立一個子執行緒,當前迴圈變數:0
[主執行緒] 即將建立一個子執行緒,當前迴圈變數:1
[子執行緒] hi number 0 from the spawned thread!
[主執行緒] 即將建立一個子執行緒,當前迴圈變數:2
[子執行緒] hi number 0 from the spawned thread!
[主執行緒] 即將建立一個子執行緒,當前迴圈變數:3
[子執行緒] hi number 0 from the spawned thread!
[主執行緒] 即將建立一個子執行緒,當前迴圈變數:4
[子執行緒] hi number 0 from the spawned thread!
[主執行緒] hi number 0 from the main thread!
[子執行緒] hi number 0 from the spawned thread!
[子執行緒] hi number 1 from the spawned thread!
[子執行緒] hi number 1 from the spawned thread!
[子執行緒] hi number 1 from the spawned thread!
[子執行緒] hi number 1 from the spawned thread!
[主執行緒] hi number 1 from the main thread!
[子執行緒] hi number 1 from the spawned thread!
[主執行緒] hi number 2 from the main thread!
[child-1]hi I am spawned thread!
[child-2]hi I am spawned thread!
[main]hi I am main thread!

訊息傳遞

以下示例演示了子執行緒獲得了主執行緒的傳送者 tx,並呼叫了它的 send 方法傳送資料,然後主執行緒就通過對應的接收者 rx 接收到了傳送的資料。

無法在子執行緒中傳送而在另外一個子執行緒中接收

示例程式碼:

use std::sync::mpsc;
use std::thread;
fn main() {
    println!("Hello, world!--->訊息傳遞");
    let p1 = Person {
        active: true,
        name: String::from("cml"),
        email: String::from("cnaylor@163.com"),
        sign_in_count: 99,
        tuple: (8, 8),
    };
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        tx.send(p1).unwrap();
    });
    let received = rx.recv().unwrap();
    println!("Got: {:?}", received);
}
#[derive(Debug)]
struct Person {
    active: bool,
    name: String,
    email: String,
    sign_in_count: u64,
    tuple: (u32, u32),
}

以上程式碼輸出:

Hello, world!--->訊息傳遞
Got: Person { active: true, name: "cml", email: "cnaylor@163.com", sign_in_count: 99, tuple: (8, 8) }

互斥鎖

互斥鎖(mutex)是 mutual exclusion 的縮寫,也就是說,任意時刻,其只允許一個執行緒訪問某些資料。為了訪問互斥器中的資料,執行緒首先需要通過獲取互斥器的 鎖(lock)來表明其希望訪問資料。鎖是一個作為互斥器一部分的資料結構,它記錄誰有資料的排他訪問權。因此,我們描述互斥器為通過鎖系統 保護(guarding)其資料。

以下示例演示了在主執行緒中建立一個值,並在多個子執行緒中修改此值,最終等所有子執行緒處理完畢,主執行緒列印最終值。為了讓變數能夠跨執行緒之間共享,引入了std::sync::Arc 這個結構體。

示例程式碼:


use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
    println!("Hello, world!--->互斥鎖");
    //Arc 原子引用計數器,確保在多個執行緒中共享資料;counter初始值為0    
    let counter = Arc::new(Mutex::new(0));
    println!("counter 初始值為:0");
    //定義一個子執行緒集合
    let mut handles = vec![];
    for _ in 0..10 {
        //將counter值從主執行緒中克隆,並賦值給私有變數 counter
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            //獲取互斥鎖,並將counter值加一
            let mut num = counter.lock().unwrap();
            *num += 1;
            let thread_id = thread::current().id();
            println!("[子執行緒:{:?}]中將 counter 的值修改為:{}", thread_id, num);
        });
        //將建立的子執行緒儲存到子執行緒集合中
        handles.push(handle);
    }
    //將所有的子執行緒join到主執行緒,確保主執行緒等待所有子執行緒執行成功
    for handle in handles {
        handle.join().unwrap();
    }
    let result = *counter.lock().unwrap();
    println!("Result: {}", result);
}

以上程式碼輸出:

Hello, world!--->互斥鎖
counter 初始值為:0
[子執行緒:ThreadId(2)]中將 counter 的值修改為:1
[子執行緒:ThreadId(4)]中將 counter 的值修改為:2
[子執行緒:ThreadId(3)]中將 counter 的值修改為:3
[子執行緒:ThreadId(5)]中將 counter 的值修改為:4
[子執行緒:ThreadId(8)]中將 counter 的值修改為:5
[子執行緒:ThreadId(6)]中將 counter 的值修改為:6
[子執行緒:ThreadId(7)]中將 counter 的值修改為:7
[子執行緒:ThreadId(9)]中將 counter 的值修改為:8
[子執行緒:ThreadId(10)]中將 counter 的值修改為:9
[子執行緒:ThreadId(11)]中將 counter 的值修改為:10
Result: 10

程式碼

本文示例程式碼維護在gitee上面:https://gitee.com/naylor_personal/rust-hello-world

開啟程式碼倉庫中的 Sport 資料夾,有驚喜!!!

說明

  • 勘誤:本文篇幅較長、涉及內容較多,若閱讀過程種發現錯誤,歡迎批評指正
  • 目的:本文絕非傳統意義上面的技術博文,僅作為學習rust的筆記,或者說是學習rust的一個教程。文中涵蓋rust大部分基礎知識,在實際開發過程中可以作為手冊翻閱
  • 心得:本文主要使用下班和週末時間斷斷續續編寫,歷時超過一個月。縱觀整個過程,開頭和結尾是喜悅的,中間的過程是相當的枯燥。開頭為即將掌握一門新語言的 hello,world 編寫方式而感到興奮。結束後回顧了整個過程,發現除了學會了rust 的hello,world 之外,其實也同步回顧了一些已經使用過的其他語言的基礎知識,尤其是java。中間過程是對未知事物的探索和對自我理解的求證,簡單來說就是先得想辦法索取rust中有哪些內容,然後形成自己的理解,最後在整理文章和編寫示例程式碼的時候不斷推導和驗證自己的理解是否正確。

引用

相關文章