rust語法

rust語法

前言

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

1、數據類型

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

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

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、語法結構

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 :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() 可變引用迭代器,可變引用元素

3、全部權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。

4、包管理 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(){}

5、錯誤處理

運用策略:

  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、標準庫

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!

7、併發

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

  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、自動化測試

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,"出錯");
}

9、面向對象

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

rust具備面向對象的設計能力,但不強制使用面向對象,由於封裝是須要代價的。不要在細顆粒度下使用面向對象,避免過分包裝。

多態技術 動態性 同構性
範型 靜態 同構
特徵對象 動態 異構
  • 靜態:編譯期肯定真實調用
  • 異構:構造的複合結構的類型能夠不相同

面向對象接口設計建議:

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

10、命令行接口cli

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

  • cargo : 項目管理工具
    • new :建立項目
    • build :構建
    • publish :發佈到網站
    • login :登陸crates.io 共享網站
    • test :運行測試
    • install :從網站下載程序
    • run :運行
  • rustc : 編譯器
相關文章
相關標籤/搜索