Rust極簡教程


簡介

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::
  • 使用 * 號導入一個 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中有哪些內容,然后形成自己的理解,最后在整理文章和編寫示例代碼的時候不斷推導和驗證自己的理解是否正確。

引用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM