rust語法


rust語法

前言

rust是什么?rust是一門新型的編程語言,目的是取代c/c++做為一個系統開發級別的編程語言。它有着c的底層高效,有着c++的現代描述能力,同時保持足夠的潔簡。作為一個新語言,他在吸取現代語言精華的同時,也避免其中設計中的一些繁瑣之處,同時它已經被許多大型的開發項目所采用,有着良好的商業化前景。

一、數據類型

rust屬於靜態類型語言。即編譯時必須知道所有變量的類型。

let 模式:數據類型 = 表達式; //定義不變量
let mut 模式:數據類型 = 表達式; //定義變量

1.1 標量scalar

1.1.1 整型

長度 有符號 無符號
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
架構 isize usize

1.1.2 浮點數

長度 名稱
32-bit f32
64-bit f64

采用IEEE-754標准表示。

1.1.3 布爾

類型 真值 假值
bool true false

1.1.4 字符類型

類型 值樣例
char 'A'

字符是unicode標准編碼的標量值。

1.2 復合compound

將多個數據類型組合成一個新的類型。

1.2.1 元組tuple

類型樣例 值樣例
(i32,char,u8) (500,'B',1)

模式解構元組:

let (x,y,z)=(1,'A',3);

1.2.2 數組array

類型樣例 值樣例
[i64;5] [1,2,3,4,5]

數組是固定長度,且元素類型相同。

1.2.3 結構struct

類型樣例 值樣例
struct User{字段序列} User{鍵值對序列}
struct User{age:u8,active:bool} User{age:18,active:true}
struct User(u8,bool); User(18,true)
struct User; User

1.2.4 枚舉enum

類型樣例 值樣例
enum 名稱{類結構序列} enum Message{Quit,Move{x:u8,y:u8},Write(u8,u8)}

1.3 切片slice

對數組的某個片段建立投影視圖的類型。切片是動態大小類型,因此只能使用切片引用。

類型樣例 值樣例
str let a:&str = &"hello"[2..4];
[T] let a:&[i8] = &[1,2,3][0..3];

1.4 引用(借用)reference

引用是指針(保存地址),指向具體數據。在rust中,引用是沒有數據所有權,而只是”借用“使用權的特殊類型。

類型樣例 值樣例
&類型 let i :&int= &3; let j :int=*i;

1.5 智能指針smart pointers

智能指針指的是能自動釋放相關資源的一系列數據結構。它表現形式類似指針,但大多數情況都擁有數據所有權,因此概念上和rust引用不相干。

常見智能指針:

  • Box : 用於在堆上分配值
  • Rc :引用計數,可實現多個擁有者
  • Ref 、RfeMut 、RefCell : 運行時借用規則
  • String :字符串
  • Vec :向量集合

用途:嵌套類型、大數據轉移所有權、特征接口。

智能指針一般需要實現:解引用特征和析構特征。

解引用特征:

use std::ops::Deref;//解不可變引用
// DerefMut //解可變引用
struct MyBox<T>(T);
impl<T> Deref for MyBox<T>{
    type Target = T;

    fn deref(&self)->&T{
        &self.0
    }
}
let a = Mybox(3);
let b = *a;// *a = *(a.deref()) = *(&T) = 3 

//傳參時會自動解引用強制多態deref coercions。
fn f(x:&i32){println!("x={}",*x);}
f(&a); // &a = &(*a) = &(3)

析構特征:

impl<T> Drop for MyBox<T>{
    fn drop(&mut self){
        println!("自動釋放資源。");
    }
}

fn main(){
    let c = MyBox(3);
    drop(c);//手動釋放
}

1.6 原生指針raw pointers

rust通過引用來實現move的補充“借用”,同時用規則和編譯檢查來保證引用的安全性。但是某些時候還是需要類似c語言的原生指針。

let mut a=3;
let b = &a as *const i32; //指向不可變數據
let c = &mut a as *mut i32;//指向可變數據
let d = 0x234usize;
let e = d as *const i32;//指向指定地址。
unsafe{//只能在不安全塊中解引用,得到指向的數據
    println!("*b={},*e={}", *b, *e);
}

1.7 函數指針

fn fa(f:fn(i32)->i32, a:i32)->i32{
    f(a) + f(a) //通過函數指針f調用函數
}
fn fb(x:i32)->i32{
    x + x
}
let a = fa(fb, 5);//a=20;

1.8 高級類型

rust 特殊用途的類型。

//別名類型。可命名的等價類型
type Myint = i32;

//從不返回類型never type:"!"
fn bar() ->!{
    loop{}
}

//動態大小類型:str、特征
let s:str = "hi"; //error.無法確定str類型靜態大小
let s:&str = "hi"; //&str 由指針和長度組成,大小等於 usize*2
let s:&std::fmt::Display = &"hi"; //str實現了Display特征,而特征是動態大小,所以只能建立特征的引用

//用於確定范型參數大小的特征:sized
fn f<T:sized>(x:T){} //sized說明T是有靜態大小的,sized可省略,自動添加
fn f2<T:?sized>(x:&T){}//?sized表示sized可選,因而x只能是引用類型&T。

二、語法結構

2.1 模式匹配

rust通過模式匹配來實現靈活的判斷和定義變量。

模式種類:

  1. 不可反駁irrefutable,即必然匹配,用於定義
  2. 可反駁refutable,可選匹配,用於判斷
let a=3; //模式a必然匹配
if let Some(b)=None{}; //模式Some(b)可以不匹配
match a{
    1 => ,//字面值模式
    1 | 2 => , //1 或 2
    4...9 => , //4 到 9
    3 if 3<2 =>, //匹配守衛,添加更多判斷
    _ => ,//必然匹配占位符
}

//對復合結構的解構匹配
struct Point{
    x:i32,
    y:i32,
}
let c = Point{x:1,y:2};
let Point{x,y} = c; //解構出x=1, y=2兩個變量

let &Point{x:x2,y:y2}= &c;//對引用進行解構,得到x2,y2
let (_,d,..)=(1,2,3,4,5); //解構元組,忽略第一個值,得d=2,忽略剩余值  

if let e@1...5 = d {println!("e={}",e)};//@綁定匹配的值

2.2 函數

格式 范例
fn 函數名(參數列表)->返回類型{函數體} fn main(){}

函數頭部由函數名、參數列表和返回類型組成,參數列表是由逗號分隔的一系列變量組成;函數體由一系列語句(statements)和一個可選的表達式(expressions)結尾組成。

語句沒有返回值,以分號結尾。表達式有返回值,沒有分號結尾。

2.3 分支

2.3.1 if 分支

格式 范例
if 布爾表達式 {真值語句體} else {假值語句體} if 5>4 {} else {}

if分支結構只會執行其中一條分支。if 分支結構是一個表達式。

2.3.2 match 分支

格式 范例
match 變量{模式=>返回值,...} match m{Message::Quit=>1,Message::Write(x,y)=>x+y,Message::Move=>0}

模式匹配必須窮盡所有情況。“_”下划線作為模式表示匹配所有。

2.3.3 if let 分支

格式 范例
if let 模式=變量{...} if Message::Quit=m{1}

if let匹配指定模式,忽略其他情況。

2.4 循環

格式 范例
loop{循環語句體} loop{}
while 布爾表達式{循環語句體} while 5>4 {}
while let 模式{循環體} while let Some(t)= s.pop(){}
for 元素 in 集合{循環語句體} for i in [1,2].iter(){}

2.5 impl 塊

impl塊可以將函數或方法關聯到結構struct、枚舉enum。

格式 范例
impl 結構名{函數列表} impl User{fn hello(&self){}}

2.6 范型

取代特定類型的占位符。通過特征trait描述占位符具備的通用能力。

rust范型支持特例化技術。即對特定類型能定義不同的操作邏輯。

范型最終會單態化monomorphization,即編譯為特定類型的代碼,因此不會有性能的開銷,但會產生多份代碼的數據占用。

格式 范例
基礎結構 名稱<占位符列表>... fn f<T,U>(x:T,y:U){}

2.6.1 范型的默認類型

trait Add<RHS=Self>{ //RHS默認類型是對象類型自身
    type Output;
    fn add(self,rhs:RHS)->Self::Output;
}
struct A(i32);
impl Add for A{
    type Output = i32;
    fn add(self, r:A)->i32{self.0 + r.0}
}
let a = A(3);
let b = A(4);
println!("a+b={}", a.add(b));//a+b=7;

2.7 特征trait

trait 類似接口。

  1. 特征和實現他的類型之一必須位於本地項目crate。
  2. 特征可以有默認實現。
  3. 特征可以有關聯類型,該類型類似范型占位符,但只有一個實現
  4. 特征可以有父特征

特征定義:

格式 范例
trait 名稱{聲明序列} trait s{fn t();}

實現特征:

格式 范例
impl 特征 for 結構{實現序列} impl s for m{fn t(){}}
變量 :impl 特征 fn f(t :impl s){}
范型占位符 : 特征 fn f<T:s>(t :T){}
同上 fn f (t :T) where T:s{}

2.7.1 特征的關聯類型

關聯類型只允許一個實現,而范型可以有n個實現。

trait IA{
    type Item;//關聯類型
    fn print(&self)->Self::Item;
}
struct A;
impl IA for A{
    type Item = i32;
    fn print(&self)->Self::Item{3}
}
let a = A;
let b = a.print(); //b = 3i32

2.8 特征對象trait object

特征對象是rust面向對象技術的基礎之一,實現同一接口多態調用。

特征對象要求:

  1. 返回類型不為Self(即該特征對象的真實類型)
  2. 方法沒有任何范型類型參數
格式 范例
&特征 let a:&Drop = &"3".to_string();

2.9 閉包closures

閉包是可以存儲到變量,捕獲調用者作用域的值的匿名函數特征。

格式 范例
|參數|{} let expr = |n|{n*2};
fn fa()=>Box<Fn()->i32>{
    let a = 3;//局部變量
    Box::new(move||a) //閉包捕獲a,但fa返回閉包,因此閉包生命周期比fa更長,默認捕獲的a無效(通過引用),move關鍵字將a所有權轉移到閉包。
    //Fn閉包是一種特征,因此無大小,需用智能指針Box包裝
}
let a = fa()(); //a=3

2.10 迭代器

trait Iterator{
    type Item;
    fn next(&mut self) ->Option<Self::Item>;
}
迭代器 作用
into_iter() 所有權迭代器
iter() 可變引用迭代器,引用元素
iter_mut() 可變引用迭代器,可變引用元素

三、所有權ownership

rust語言為了高效管理內存,使用所有權概念取代垃圾回收機制和手工管理內存。

棧stack和堆heap是內存的兩個區域。棧存放作用域上下文的變量,而堆存放自由分配的變量(通過指針使用)。

rust的堆中變量賦值是移動move語義,而非淺拷貝shallow copy或深拷貝deep copy。

因此所有權的意義是:值的所有者只有一個。該所有者離開作用域即丟棄值

引用類型對move語義進行了補充,它不獲得所有權,而只有使用權。為了保證數據有效性,編譯器對引用對象的生命周期進行嚴格的檢查和假定,默認函數無法通過引用返回對象。

關於引用(&類型):

  1. 為了避免所有權的轉移,使用引用類型來解決。
  2. 為了避免數據競爭data race,同一作用域變量如果有一個可變引用,即不可擁有其他引用。
  3. 編譯器將會檢查引用的有效性,避免懸垂引用。
  4. 為了增強編譯器檢查的能力(如函數返回引用),使用生命周期注解技術。

獲取值的三種方式:

方式 所有權 生命周期 相關特征 關鍵字
T 轉移 自動 FnOnce move
&T 編譯檢查 Fn
&mut T 編譯檢查 FnMut

3.1 生命周期lifetime

變量都有生命周期,按定義的順序在作用域末尾逆序終結。引用自身的生命周期必須短於所引用對象的生命周期,否則出現懸垂引用(指向無效對象的指針)。

默認在同一個函數內可以通過借用檢查器borrow checker自動判斷,但是跨函數傳遞引用就無法自動處理。因為函數可被任意上下文調用,所以無法假定引用參數應該具備怎樣的生命周期。

生命周期注解是一種針對引用參數和擁有引用的復合結構與函數的范型占位符。它可以將幾個變量的生命周期進行關聯,聲明具備“合理一致”的生命周期,從而讓編譯器取得檢查引用有效性的必要信息。

所謂“合理一致”指的編譯器對關聯的參數和返回值做出合理假設。如:

  1. 返回值是被注解的參數之一,因此“應該”擁有他們中生命周期最短的一個

對於結構變量:

  1. 結構本身生命周期應該短於被注解的字段
格式 范例
&'注解 類型 fn f<'a>(x:&'a i32){}
struct 結構<'占位符>{引用成員類型注解} struct s<'a>{p: &'a str,}
fn 函數(注解參數列表)->注解返回類型{} fn f<'a>(x:&'a){}
程序生命周期:&'static let s:&'static str="str";
let a = 3;
let mut p =&a;
let mut p2;
let b = 4;
p = &b; //error,p比b長壽,因為b在p之后定義,即b先於p終結

fn f<'a>(x:&'a isize,y:&'a isize)->&'a isize{
    x
}
p2 = f(&a,&b);//error, p2比f(a,b)長壽。因為雖然a比p2長壽,但b比p2短命,因此f(a,b)=b, b<p2,即使實際返回的是a

struct C<'a>(mut &'a isize);
let mut c = C(&b);
let d = 5;
c.0 = &d;//error, c.0 < c,被注解字段必須比結構長壽

//生命周期子類型lifetime subtyping
struct E<'e, 'c :'e>{c:&'e  C<'c>}
struct E2<'e, T :'e>{c:&'e  T} //類似定義
let e = E2{c:&c};
fn fe(x:C)->&isize{
    E{c:&x}.c.0 //ok, 雖然E是臨時變量,x是move進來的參數,但E的定義讓其擁有兩個格外生命周期:E::c 和C::0,其中E自身的生命周期對應臨時變量,E::c對應x,而C::0只是被注解省略,對應->& isize
}

3.1.1 注解省略

為了編碼方便,以下情況可以省略注解:

  1. 單個待注解參數
  2. 其中一個參數是&self 或&mut self。

四、包管理 cargo

現代語言相對傳統語言,個人認為最大的便利在於有一個設計良好的包管理系統。如何組織代碼,是一門管理學問,將有助我們軟件工程的建設。

4.1 包 package

./Cargo.toml 工程描述文件,可以是一個項目,也可以是多個項目的工作區。

[package]
name="包名"
version = "0.1.1"

4.2 項目 crate

項目一個到n個crate(箱)組成,一個crate對應一個.rs源文件。

./src/main.rs 項目根,0-n個
./src/lib.rs 項目根(庫),0-1個
./src/bin/* 項目根(非默認項目)

庫項目可以被其他項目導入使用。

一般同時需要修改工程描述文件,添加依賴:

#Cargo.toml
[dependencies]
mylib = { path = "../mylib"}

4.3 模塊

注意,當前rust語言有兩個版本,最新版的模塊系統已經進行了大改,請務必使用nightly每晚編譯版本rust。

crate可以划分模塊。一個crate可以有1個到n個模塊。默認模塊名x對應x.rs,其中根為固定的”crate“名。

例子:

   # :: 外部模塊前綴
./src/main.rs   #crate 根模塊(隱藏)
./src/a.rs      #crate::a
./src/b.rs      #crate::b
./src/c.rs  #crate::c
./src/c/d.rs    #crate::c::d

權限規則:

  1. 模塊內所有項(對外層)默認私有
  2. 即同模塊的項可以訪問
  3. 即父模塊的項可以訪問
  4. 用pub定義的項外層可以訪問

源文件定義:

//main.rs -- crate
mod a; //在根層即根同目錄找a.rs定義
mod b{
    pub fn f2();
} //內聯,將隱藏b.rs文件
mod c;
fn main(){
    a::f();
    b::f();//error。f不存在被隱藏
    c::f();//error.私有
    c::d::f(); //d 必須在c/mod.rs定義為pub mod d;
}
fn f(){}
//a.rs -- crate::a
pub fn f(){}
fn f2(){}
//b.rs -- crate::b
use c::d; //use使用的永遠是絕對路徑,等於crate::c::d,而不是crate::b::c::d。
fn f(){
    d::f2(); //ok
    c::d::f2(); //因為use crate::c::d;,所以導入了指定crate的模塊信息到當前模塊,所以c::d::f2()不會被識別為crate::b::c::d::f2()
}
//c.rs -- crate::c
pub mod d;
fn f(){
    d::f2(); //使用相對路徑,等於crate::c::d::f2();
    self::d::f2(); //等於上面
}
//c/d.rs -- crate::c::d
pub fn f(){
    crate::a::f();//用絕對路徑::訪問其他模塊
    crate::a::f2();//error a不是d的父親,d不能訪問其私有成員
    crate::b::f2();//訪問的是在main.rs中的定義
    super::f();//等價crate::c::f(), ok
    super::super::f();//相對路徑訪問main.rs 的f()
}
pub fn f2(){}

五、錯誤處理

運用策略:

  1. 默認Result
  2. 原型設計panic!
  3. 無需處理錯誤panic!
  4. 違反調用契約panic!

5.1 不可恢復錯誤 panic!

Cargo.toml 設置:

[profile.release]
panic = 'abort' #直接終止程序
#或者展開回溯unwinding

使用演示:

panic!("error message!");

5.2 可恢復錯誤 Result

enum Result<T,E>{
    Ok(T),
    Err(E),
}

let f = File::open("hello.txt").expect("自動報錯。"); //.unwrap();自動報錯,使用內部信息
let mut s = String::new();
f.read_to_string(&mut s)?; //?自動向調用方返回錯誤對象。

六、標准庫

6.1 集合

6.1.1 vector

let mut v:Vec<i64> = Vec![1,2,3];
v.push(5);
let el :i64 = v[1];
let el2 :Option<i64> = v.get(1);
for i in &mut v{
    *i += 123;
}

6.1.2 String

StringVec<u8>的包裝。

let mut s = String::from("hello world.");
s.push_str("man.");
format!("{}-{}-{}", s,s,s)
let c :char = s.chars()[1];
let c2 :u8 = s.bytes()[2];
let c3 :&str = &s[3..];

6.1.3 map

HashMap<K,V>

use std::colletions::HashMap;
let mut m= HashMap::new();
m.insert(String::from("yes"),0);
let v :Option<_> = m.get(&String::from("yes"));

6.2 智能指針工具

6.2.1 Box

基本的堆引用,具有所有權。

let a = Box::new(3);//3存放在堆上,a有所有權
let b:i32 = *a;//解引用,取得堆上值3

6.2.2 Rc

引用計數(多所有權)。

use std::rc::Rc;

let a = Rc::new(3);
let b = Rc::clone(&a);//引用計數+1
let c = Rc::clone(&a);//引用計數2

6.2.3 Refcell

返回內部可變引用

let a =Rc::new(RefCell::new(3));//創建內部可變3
let b =Rc::clone(&a);
let c =Rc::clone(&a);
*a.borrow_mut()+=1; //返回內部可變引用

6.2.4 Weak

弱引用,沒所有權。可用於解決循環引用問題。

let a=Rc::new(3);
let b:Weak<_>=Rc::downgrade(&a);//從Rc引用中創建弱引用
drop(a); //手動刪除a
println!("b={}",b.upgrade().expect("無法訪問b"));

6.3 宏

//生成自動打印內部信息的特征
#[derive(Debug)]
struct A(&str, i8);
println!("{:?}", A("hi", 3));

//生成等於、不等特征
#[derive(PartialEq)] //Eq
struct B;
println!("B==B?{}", B==B);

//生成有序特征
#[derive(Ord,PartialOrd,Eq,PartialEq,Debug)]
struct C(i8);
println!("{:?}>{:?}?{}",C(3),C(1),C(3)>C(1));

//生成克隆特征(要求字段實現克隆特征才有效果)
#[derive(Clone)] //Copy
struct D(i8);
let mut a=D(3);
let b = a.clone();
let pa = a.0 as *const i8;
let pb = b.0 as *const i8;
println!("&{:?}->&{:?}={}",pa,pb,b.0);

//生成哈希特征
#[derive(Hash)]
struct E;

//生成默認值特征
#[derive(Default)]
struct F;

6.4 命令行程序

  • println!
  • eprintln!

七、並發

並行處理,會面對如下問題:

  1. 競爭狀態race conditions
  2. 死鎖deadlocks
  3. 復雜的除錯debug過程

7.1 線程

thread::spawn()

use std::thread;

let a = 3;
let handle = thread::spawn(move||{
    println!("新線程。a={}", a);//move轉移a的所有權
});

handle.join().unwrap();//主線程等待新線程執行完畢
//否則主線程執行完畢會自動關閉子線程

7.2 消息通道

新興的一種協調並發處理的方式。易用,單擁有權。

use std::sync::mpsc;//多產單消multiple producer,single consumer
use std::thread;

let (tx,rx)=mpsc::channel();
thread::spawn(move ||{tx.send("hi".to_string()).unwrap();});

println!("{}",rx.recv().unwrap());//獲取“hi”

7.3 共享狀態

互斥器mutex(mutual exclusion):任意時刻,只允許一個線程訪問數據。多擁有權。

use std::sync::Mutex;
use std::sync::Arc; //原子性Rc
use std::thread;

let a = Mutex::new(3);
{
    let mut b :MutexGuard<_>= a.lock().unwrap();//獲取互斥器內部數據的智能指針
    //該指針擁有鎖,並能自動釋放鎖
    *b+=1;
}//自動釋放鎖
println!("a={:?}",a);//可再次讀取數據

//多線程共享互斥器
let b = Arc::new(Mutex::new(3));//Arc是跨線程安全的Rc,即多所有權智能指針
let c = Arc::clone(&b);//增加計數
let handle1 = thread::spawn(move||{
    let n = c.lock().unwrap();//共享同一個鎖
    *n += 1;
});
let d = Arc::clone(&b);//計數3
let handle2 = thread::spawn(move||{
    let n = d.lock().unwrap();//共享同一個鎖
    *n += 2;
});
handle1.join().unwrap();
handle2.join().unwrap();
println!("b={}", *b.lock().unwrap());

八、自動化測試

8.1 單元測試

#[cfg(test)] //只在配置是test編譯
mod tests{
    use super::*; //導入全部外部模塊

    #[test] //測試項目
    fn exploration()->Result<(),String>{
        assert!(3>2);
        panic!("測試失敗。");
        Err(String::from("Result失敗"))
    }

    #[test]
    #[should_panic] //希望出現異常,否則不通過
    fn g(){
        assert!(3<2);
    }
}

8.2 集成測試

構建目錄:/tests/,每個.rs作為獨立crate構建。

use adder; //導入待測試模塊

#[test]
fn g(){
    assert!(2>3,"出錯");
}

九、面向對象

面向對象:封裝並自行維護內部狀態的結構體,在相同接口下多態調用。

rust具有面向對象的設計能力,但不強制使用面向對象,因為封裝是需要代價的。不要在細顆粒度下使用面向對象,避免過度包裝。

多態技術 動態性 同構性
范型 靜態 同構
特征對象 動態 異構
  • 靜態:編譯期確定真實調用
  • 異構:構造的復合結構的類型可以不相同

面向對象接口設計建議:

  1. 被動方,需要改動自身狀態的
  2. 封裝自身狀態必須的輸入輸出過濾
  3. 思考單對象,雙對象,多對象下的設計差異

十、命令行接口cli

一些命令行下的編譯管理工具:

  • cargo : 項目管理工具
    • new :創建項目
    • build :構建
    • publish :發布到網站
    • login :登錄crates.io 共享網站
    • test :運行測試
    • install :從網站下載程序
    • run :運行
  • rustc : 編譯器


免責聲明!

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



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