Rust Tips

RustTips

github地址:https://github.com/bradyjoestar/rustnotes(歡迎star!)

Rust 和其它語言最大的特點,就是它的所有權(quán)系統(tǒng),及在此基礎(chǔ)之上構(gòu)建的編程模型。

1.綁定

重要:首先必須強調(diào)下,準確地說Rust中并沒有變量這一概念,而應(yīng)該稱為標識符目標資源(內(nèi)存,存放value)綁定到這個標識符。

2.move

在Rust中,和“綁定”概念相輔相成的另一個機制就是“轉(zhuǎn)移move所有權(quán)”,意思是,可以把資源的所有權(quán)(ownership)從一個綁定轉(zhuǎn)移(move)成另一個綁定。

let move

這個操作同樣通過let關(guān)鍵字完成,和綁定不同的是,=兩邊的左值和右值均為兩個標識符:

語法:
    let 標識符A = 標識符B;  // 把“B”綁定資源的所有權(quán)轉(zhuǎn)移給“A”

move前后的內(nèi)存示意如下:

Before move:
a <=> 內(nèi)存(地址:A,內(nèi)容:"xyz")
After move:
a
b <=> 內(nèi)存(地址:A,內(nèi)容:"xyz")

被move的變量不可以繼續(xù)被使用。

參數(shù) move

除了通過let關(guān)鍵字外,還有一種move,是直接作為方法或者函數(shù)的參數(shù),導(dǎo)致發(fā)生了move
代碼示例:

fn main() {
    let mut a = String::from("life cycle test");

    move_test(a);

    println!("{:?}",a);
}

fn move_test(s: String) {
    println!("{:?}",s)
}

編譯過程中將會拋出以下的錯誤:

error[E0382]: borrow of moved value: `a`
  --> src/main.rs:18:21
   |
14 |     let mut a = String::from("life cycle test");
   |         ----- move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait
15 | 
16 |     move_test(a);
   |               - value moved here
17 | 
18 |     println!("{:?}",a);
   |                     ^ value borrowed here after move
copy和clone

Copy 和 Clone 兩者的區(qū)別和聯(lián)系有:

  • Copy內(nèi)部沒有方法,Clone內(nèi)部有兩個方法。
  • Copy trait 是給編譯器用的,告訴編譯器這個類型默認采用 copy 語義,而不是 move 語義。- - Clone trait 是給程序員用的,我們必須手動調(diào)用clone方法,它才能發(fā)揮作用。
  • Copy trait不是你想實現(xiàn)就實現(xiàn),它對類型是有要求的,有些類型就不可能 impl Copy。Clone trait 沒有什么前提條件,任何類型都可以實現(xiàn)(unsized 類型除外)。
  • Copy trait規(guī)定了這個類型在執(zhí)行變量綁定、函數(shù)參數(shù)傳遞、函數(shù)返回等場景下的操作方式。即這個類型在這種場景下,必然執(zhí)行的是“簡單內(nèi)存拷貝”操作,這是由編譯器保證的,程序員無法控制。Clone trait 里面的 clone 方法究竟會執(zhí)行什么操作,則是取決于程序員自己寫的邏輯。一般情況下,clone 方法應(yīng)該執(zhí)行一個“深拷貝”操作,但這不是強制的,如果你愿意,也可以在里面啟動一個人工智能程序,都是有可能的。
  • 如果你確實需要Clone trait執(zhí)行“深拷貝”操作,編譯器幫我們提供了一個工具,我們可以在一個類型上添加#[derive(Clone)],來讓編譯器幫我們自動生成那些重復(fù)的代碼。
版權(quán)聲明:本文為CSDN博主「qiangshou001」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/piedaocaoren/java/article/details/86692822

任何簡單標量值的組合可以是Copy的,對于實現(xiàn)了Copy Trait的類型來說,當移動發(fā)生的時候,它們可以Copy的副本代替自己去移動,而自身還保留著所有權(quán)。

  • 所有整數(shù)類型,比如u32。
  • 布爾類型,bool,它的值是true和false。
  • 所有浮點數(shù)類型,比如f64。
  • 元組,當且僅當其包含的類型也都是Copy的時候。如:(i32, i32)是Copy的,不過(i32, String)就不是。
作者:soojade
鏈接:http://www.itdecent.cn/p/64d54d39cffb
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

3.方法 函數(shù)

rust 中方法,函數(shù),trait和 go中的方法,函數(shù),interface有點相似。

go中的方法和函數(shù)
package main

import "fmt"

type Person struct {
    Name string
}

func main() {
    person := Person{
        Name: "person",
    }

    person.printName()

    ptr := &person

    ptr.printfName()

    ptr.printName()

    person.printfName()

    printPerson(person)
}

func (c Person)printName(){
    fmt.Println(c.Name)
}

func (c *Person) printfName(){
    fmt.Printf("%s\n",c.Name)
}

func printPerson(person Person){
    fmt.Println(person)
}

運行結(jié)果:

person
person
person
person
{person}
rust中的方法和參數(shù)
#[derive(Debug)]
pub struct MBtree {
    pub name: String,
    pub age: i64,
    pub number: i64,
}

impl MBtree {
    pub fn new(name: String, age: i64, number: i64) -> MBtree {
        MBtree { name, age, number }
    }

    pub fn print_name(self) -> () {
        println!("{:?}", self.name)
    }

    pub fn print_age(&self) -> () {
        println!("{:?}", self.age)
    }

    pub fn print_number(&mut self) -> () {
        println!("{:?}", self.number);
        self.number = 12;
        println!("{:?}", self.number);
    }
}


pub fn print_mbtree_number(mbtree: &mut MBtree) -> () {
    println!("{:?}", mbtree.number)
}

pub fn print_mbtree_age(mbtree: &MBtree) -> () {
    println!("{:?}", mbtree.age)
}

pub fn print_mbtree_name(mbtree: MBtree) -> () {
    println!("{:?}", mbtree.name)
}


fn main() {
    let mut mbtree = MBtree::new(String::from("mbtree"), 21, 121);

    mbtree.print_age();

    println!("{:?}", mbtree.age);

    mbtree.print_number();

    mbtree.print_age();

    print_mbtree_age(&mbtree);

    print_mbtree_number(&mut mbtree);

    print_mbtree_name(mbtree);
}

在rust中,參數(shù)的傳遞有三種類型:

  • self
  • &self
  • &mut self
    self會消費所有權(quán),相當于move。一旦被移動,該變量被消費,后面無法繼續(xù)使用。
    在print_mbtree_name繼續(xù)打印,
    print_mbtree_name(mbtree);

    println!("{:?}",mbtree.number)

會拋出以下的錯誤:

error[E0382]: borrow of moved value: `mbtree`
  --> src/main.rs:59:21
   |
43 |     let mut mbtree = MBtree::new(String::from("mbtree"), 21, 121);
   |         ---------- move occurs because `mbtree` has type `MBtree`, which does not implement the `Copy` trait
...
57 |     print_mbtree_name(mbtree);
   |                       ------ value moved here
58 | 
59 |     println!("{:?}",mbtree.number)
   |                     ^^^^^^^^^^^^^ value borrowed here after move

error: aborting due to previous error

4.trait

go interface

在go中有著interface, 主要有著以下的特點:

  • 接口的使用不僅僅針對結(jié)構(gòu)體,自定義類型、變量等等都可以實現(xiàn)接口。
  • 如果一個接口沒有任何方法,我們稱為空接口,由于空接口沒有方法,所以任何類型都實現(xiàn)了空接口。
  • 要實現(xiàn)一個接口,必須實現(xiàn)該接口里面的所有方法。
  • 接口的實現(xiàn)是隱式實現(xiàn)

例子如下:

package main

import "fmt"


//定義接口
type Skills interface {
    Running()
    Getname() string
}

type Student struct {
    Name string
    Age int
}


// 實現(xiàn)接口
func (p Student) Getname() string{   //實現(xiàn)Getname方法
    fmt.Println(p.Name )
    return p.Name
}

func (p Student) Running()  {   // 實現(xiàn) Running方法
    fmt.Printf("%s running",p.Name)
}
func main()  {
    var skill Skills
    var stu1 Student
    stu1.Name = "wd"
    stu1.Age = 22
    skill = stu1
    skill.Running()  //調(diào)用接口
}
rust trait

rust trait有以下的特點:

  • 需要顯式地被實現(xiàn)
  • trait中可以提供默認的方法,但是可以被實現(xiàn)者重載
  • trait中的參數(shù)傳遞也有三種類型,self,&self,&mut self。

一個非常好的例子如下:

struct Sheep {
    naked: bool,
    name: String,
}

trait Animal {
    fn new(name: String) -> Self;

    // 實例方法簽名;這些方法將返回一個字符串。
    fn name(&self) -> String;
    fn noise(&self) -> String;

    // trait 可以提供默認的方法定義。
    fn talk(&self) {
        println!("{} says {}", self.name(), self.noise());
    }

    fn sleep(self: Self)
    where
        Self: std::marker::Sized,

    //rust一共提供了5個重要的標簽trait,都被定義在標準庫std::marker模塊中
    //
    // Sized trait,用來標示編譯期可確定大小的類型
    // Unsize trait,目前該trait為實驗特性,用于標示動態(tài)大小類型
    // Copy trait,用來標示可以安全地按位復(fù)制其值的類型。
    // Send trait,用來標示可以跨線程安全通信的類型
    // Sync trait,用來標示可以在線程間安全共享引用的類型。

    //If we dont add `where Self: std::marker::Sized`, It will report the following error:
    //error[E0277]: the size for values of type `Self` cannot be known at compilation time
    //   --> src/main.rs:18:14
    //    |
    // 18 |     fn sleep(self: Self)
    //    |              ^^^^       - help: consider further restricting `Self`: `where Self: std::marker::Sized`
    //    |              |
    //    |              doesn't have a size known at compile-time
    //    |
    //    = help: the trait `std::marker::Sized` is not implemented for `Self`
    //    = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
    //    = note: all local variables must have a statically known size
    //    = help: unsized locals are gated as an unstable feature
    //
    // error: aborting due to previous error
    {
        println!("{}", self.name())
    }
}

impl Sheep {
    fn is_naked(&self) -> bool {
        self.naked
    }

    fn shear(&mut self) {
        if self.is_naked() {
            // 實現(xiàn)者可以使用它的 trait 方法。
            println!("{} is already naked...", self.name());
        } else {
            println!("{} gets a haircut!", self.name);

            self.naked = true;
        }
    }
}

// 對 `Sheep` 實現(xiàn) `Animal` trait。
impl Animal for Sheep {
    fn new(name: String) -> Sheep {
        Sheep {
            name: name,
            naked: false,
        }
    }

    fn name(&self) -> String {
        self.name.clone()
    }

    fn noise(&self) -> String {
        if self.is_naked() {
            String::from("baaaaah?")
        } else {
            String::from("baaaaah!")
        }
    }

    // 默認 trait 方法可以重載。
    fn talk(&self) {
        // 例如我們可以增加一些安靜的沉思。
        println!("{} pauses briefly... {}", self.name, self.noise());
    }

    fn sleep(self){
        println!("{} {}", self.name(),self.name)
    }
}

fn main() {
    let mut dolly: Sheep = Animal::new(String::from("Dolly"));

    dolly.talk();
    dolly.shear();
    dolly.talk();

    dolly.sleep();

    // dolly moved, If we add the following code,It will report the error:
    //error[E0382]: borrow of moved value: `dolly`
    //    --> src/main.rs:106:5
    //     |
    // 96  |     let mut dolly: Sheep = Animal::new(String::from("Dolly"));
    //     |         --------- move occurs because `dolly` has type `Sheep`, which does not implement the `Copy` trait
    // ...
    // 102 |     dolly.sleep();
    //     |     ----- value moved here
    // ...
    // 106 |     dolly.talk();
    //     |     ^^^^^ value borrowed here after move
    //
    // error: aborting due to previous error

    // dolly.talk();

}

5.泛型

在go 目前的版本(1.14)中,暫時不支持泛型。

泛型在rust中使用廣泛,可以廣泛地應(yīng)用在元祖、枚舉、結(jié)構(gòu)體定義,方法,函數(shù),trait中。

結(jié)構(gòu)體和枚舉定義中的泛型
#[derive(Debug)]
struct Point<T, U> {
    x: T,
    y: U,
}

#[derive(Debug)]
enum Error<T, E> {
    NetWork(T),
    HandShake(E),
}

#[derive(Debug)]
enum Result<T> {
    Waiting,
    Processing(T),
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
    println!("{:?}", both_integer);
    println!("{:?}", both_float);
    println!("{:?}", integer_and_float);

    //consider giving `result1` the explicit type `Result<T>`, where the type parameter `T` is specified
    //編譯的時候必須知道類型
    let error1: Error<i32, i32> = Error::NetWork(12);
    let error2: Error<i32, String> = Error::HandShake(String::from("error test"));

    println!("{:?}", error1);
    println!("{:?}", error2);

    //consider giving `result1` the explicit type `Result<T>`, where the type parameter `T` is specified
    //編譯的時候必須知道類型
    let result1: Result<String> = Result::Waiting;
    let result2 = Result::Processing(String::from("result test"));
    println!("{:?}", result1);
    println!("{:?}", result2);
}

運行結(jié)果如下,需要注意的是,編譯器需要在編譯過程中可以推斷出所有的泛型的實際類型,可以具體查看上面的enum。

Point { x: 5, y: 10 }
Point { x: 1.0, y: 4.0 }
Point { x: 5, y: 4.0 }
NetWork(12)
HandShake("error test")
Waiting
Processing("result test")
覆蓋函數(shù),方法,trait的泛型例子。
use std::fmt::{Debug, Display};
mod order;

#[derive(Debug)]
struct Point<U, T>
where
    U: PartialOrd + Clone,
{
    x: U,
    y: T,
}

//This is a generics of method test
impl<U, T> Point<U, T>
where
    U: PartialOrd + Clone + Display,
{
    fn getX(&self) -> &U {
        &self.x
    }

    fn getY(&self) -> &T {
        &self.y
    }

    fn compose<S>(&self, s: S)
    where
        S: PartialOrd + Display,
    {
        println!("{} , says {}", s, &self.x)
    }
}

trait Tone {
    fn hello(&self);

    fn world<V>(&self, v: V)
    where
        V: PartialOrd + Clone + Display;
}

impl<U, T> Tone for Point<U, T>
where
    U: PartialOrd + Clone + Display,
    T: PartialOrd + Clone + Display,
{
    fn hello(&self) {
        println!("tone hello x:{},y:{}", self.getX(), self.getY())
    }

    fn world<V>(&self, v: V)
    where
        V: PartialOrd + Clone + Display,
    {
        println!("tone world,x:{},y:{},v:{}", self.getX(), self.getY(), v)
    }
}

trait Howl<V>
where
    V: PartialOrd + Clone + Display,
{
    fn hello(&self);

    fn world(&self, v: V);
}

//Not recommend, we should know the type when we compile the code
impl<U, T, V> Howl<V> for Point<U, T>
where
    U: PartialOrd + Clone + Display,
    T: PartialOrd + Clone + Display,
    V: PartialOrd + Clone + Display,
{
    fn hello(&self) {
        println!("Howl hello x:{},y:{}", self.getX(), self.getY())
    }

    fn world(&self, v: V) {
        println!("Howl world,x:{},y:{},v:{}", self.getX(), self.getY(), v)
    }
}

//This is a generics of function test
fn composer<U, T, S>(point: &Point<U, T>, s: S)
where
    S: PartialOrd + Display,
    U: PartialOrd + Clone + Display,
    T: Display,
{
    println!("{} ,and {} ,and {}", point.getX(), s, point.getY())
}

fn largest<T>(list: &[T]) -> T
where
    T: PartialOrd + Copy,
{
    let mut largest = list[0];
    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn smallest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut smallest = list[0];
    for &item in list.iter() {
        if item < smallest {
            smallest = item;
        }
    }
    smallest
}

fn main() {
    let mut point = Point {
        x: 5,
        y: String::from("point test"),
    };
    println!("{:?}", point.getX());
    println!("{:?}", point.getY());

    Point::compose(&point, String::from("compose test"));

    composer(&point, String::from("composer test"));

    println!("-------------generics tone test--------------");
    Tone::world(&point, String::from("tone test"));
    Tone::hello(&point);
    println!("-------------generics howl test--------------");
    Howl::world(&point, String::from("tone test"));
    //trait objects without an explicit `dyn` are deprecated
    (&point as &dyn Howl<String>).hello();

    println!("-------------function howl test--------------");
    let number_list = vec![34, 50, 25, 100, 65];

    let i32_largest = largest(&number_list);
    let i32_smallest = smallest(&number_list);

    println!("The largest number is {}", i32_largest);
    println!("The smallest number is {}", i32_smallest);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let char_largest = largest(&char_list);
    let char_smallest = smallest(&char_list);
    println!("The largest char is {}", char_largest);
    println!("The smallest char is {}", char_smallest);
}

6.幾種常用的trait

From和Into

From和Into會消耗目標對象的所有權(quán)。消耗舊的對象,得到一個新的對象。

use std::convert::From;

#[derive(Debug)]
struct FromNumber {
    value: i32,
}

#[derive(Debug)]
struct IntoNumber {
    value: i32,
}

impl From<IntoNumber> for FromNumber {
    fn from(item: IntoNumber) -> Self {
        FromNumber {
            value: item.value * 2,
        }
    }
}

//The Into trait is simply the reciprocal of the From trait.
//That is, if you have implemented the From trait for your type,
//Into will call it when necessary.
// 在代碼中只要實現(xiàn)了from,對應(yīng)的對象就可以進行調(diào)用into。
// into 的代碼實現(xiàn),最后還是會調(diào)用from。
// impl<T, U> Into<U> for T
// where
//     U: From<T>,
// {
//     fn into(self) -> U {
//         U::from(self)
//     }
// }

fn main() {
    let into_number = IntoNumber{
        value:2,
    };

    println!("IntoNumber is {:?}",into_number);

    // Try removing the type declaration
    let from_number:FromNumber = into_number.into();
    println!("My number is {:?}", from_number);


    // borrow of moved value: `into_number`
    // println!("IntoNumber is {:?}",into_number);

    let into_number_2 = IntoNumber{
        value:3,
    };

    println!("IntoNumber2 is {:?}",into_number_2);

    let from_number2 = FromNumber::from(into_number_2);
    println!("Fromnumber2 is {:?}", from_number2);

    // borrow of moved value: `into_number_2`
    // println!("IntoNumber is {:?}",into_number_2);
}
Borrow, BorrowMut

對于Borrow和BorrowMut,我的理解就是提供了一共函數(shù)式編程的調(diào)用方式。(關(guān)于函數(shù)式編程可以看java的lamda表達式)。

另外borrow和borrowMut在大多是情況下和&&mut 等價,但是在String類型上有一些區(qū)別。

出現(xiàn)這個問題的原因主要在于:borrow有可能得到其他內(nèi)部等價的類型的引用,例如:

let string = String::from("borrow test");

string.borrow可能得到&String,&str,但是&string一定會得到&String類型。

  • 對于一個類型為 T 的值 foo,如果 T 實現(xiàn)了 Borrow<U>,那么,foo 可執(zhí)行 .borrow() 操作,即 foo.borrow()。操作的結(jié)果,我們得到了一個類型為 &U 的新引用。Borrow 的前后類型之間要求必須有內(nèi)部等價性。不具有這個等價性的兩個類型之間,不能實現(xiàn) Borrow。

  • BorrowMut<T> 提供了一個方法 .borrow_mut()。它是 Borrow<T> 的可變(mutable)引用版本。對于一個類型為 T 的值 foo,如果 T 實現(xiàn)了 BorrowMut<U>,那么,foo 可執(zhí)行 .borrow_mut() 操作,即 foo.borrow_mut()。操作的結(jié)果我們得到類型為 &mut U 的一個可變(mutable)引用。注:在轉(zhuǎn)換的過程中,foo 會被可變(mutable)借用。

use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashMap;
use std::borrow::Borrow;

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

fn main() {
    let a = String::from("test");

    let b = &a ;

    //error[E0283]: type annotations needed for `std::string::String`
    //   --> src/main.rs:17:15
    //    |
    // 13 |     let a = String::from("test");
    //    |         - consider giving `a` a type
    // ...
    // 17 |     let c = a.borrow();
    //    |               ^^^^^^ cannot infer type for struct `std::string::String`
    //    |
    //    = note: cannot resolve `std::string::String: std::borrow::Borrow<_>`
    //
    // error: aborting due to previous error
    let c = a.borrow();
}

上述代碼編譯過程中,會拋出錯誤。
let c = a.borrow替換為以下語句,強制指定類型,可以通過:

let c:&str  = a.borrow();
asRef,asMut

AsRef 提供了一個方法 .as_ref()。

對于一個類型為 T 的對象 foo,如果 T 實現(xiàn)了 AsRef<U>,那么,foo 可執(zhí)行 .as_ref() 操作,即 foo.as_ref()。操作的結(jié)果,我們得到了一個類型為 &U 的新引用。

  • 與 Into<T> 不同的是,AsRef<T> 只是類型轉(zhuǎn)換,foo 對象本身沒有被消耗;
  • T: AsRef<U> 中的 T,可以接受 資源擁有者(owned)類型,共享引用(shared referrence)類型 ,可變引用(mutable referrence)類型。
  • rust對于內(nèi)部等價性沒有borrow那么嚴格。

AsMut<T> 提供了一個方法 .as_mut()。它是 AsRef<T> 的可變(mutable)引用版本。

對于一個類型為 T 的對象 foo,如果 T 實現(xiàn)了 AsMut<U>,那么,foo 可執(zhí)行 .as_mut() 操作,即 foo.as_mut()。操作的結(jié)果,我們得到了一個類型為 &mut U 的可變(mutable)引用。注:在轉(zhuǎn)換的過程中,foo 會被可變(mutable)借用。

asRef和asMut的使用,主要是想使用其它類型的方法,但是自己又不提供,所以可以先轉(zhuǎn)換到其它類型的引用上。

asRef和asMut可以自己實現(xiàn)。以tokio-rs/bytes為例,自定義的類,可以使用&[u8]的方法。

impl AsRef<[u8]> for BytesMut {
    #[inline]
    fn as_ref(&self) -> &[u8] {
        self.as_slice()
    }
}

impl AsMut<[u8]> for BytesMut {
    fn as_mut(&mut self) -> &mut [u8] {
        self.as_slice_mut()
    }
}

impl AsRef<[u8]> for Bytes {
    #[inline]
    fn as_ref(&self) -> &[u8] {
        self.as_slice()
    }
}

string也可以轉(zhuǎn)換為&PATH的引用,并能夠調(diào)用&PATH的方法:

#[stable(feature = "rust1", since = "1.0.0")]
impl AsRef<Path> for String {
    fn as_ref(&self) -> &Path {
        Path::new(self)
    }
}

例子如下:

use std::path::Path;

fn main() {
    let a = String::from("/root/test.txt");

    let path:&Path = a.as_ref();
    println!("{:?}",path.file_name().unwrap());
}

運行結(jié)果:

"test.txt"
ToOwned

ToOwned 為 Clone 的普適版本。它提供了 .to_owned() 方法,用于類型轉(zhuǎn)換。

有些實現(xiàn)了 Clone 的類型 T 可以從引用狀態(tài)實例 &T 通過 .clone() 方法,生成具有所有權(quán)的 T 的實例。但是它只能由 &T 生成 T。而對于其它形式的引用,Clone 就無能為力了。

而 ToOwned trait 能夠從任意引用類型實例,生成具有所有權(quán)的類型實例。


Screenshot from 2020-06-06 16-58-16.png

例子中的代碼,如下:

use std::path::Path;
use std::borrow::ToOwned;

fn main() {
    let a = "to_owned";
    let b = a.clone();

    println!("{:?}",a);

    let mut c:String = a.to_owned();
    c.push_str(" test");
    println!("{:?}",c)
}

運行結(jié)果:

"to_owned"
"to_owned test"

to_owned一般比較很少用,即使在tokio-rs/hyper中都基本沒怎么用到。

7. 指針

Rust 中的指針大體可以分為以下三種:

  • 引用 references
  • 智能指針 smart pointers
  • 裸指針 raw pointers

引用

就是直接對一個變量執(zhí)行 &、&mut 操作,永遠不會為 null。
借用與引用是一種相輔相成的關(guān)系,若B是對A的引用,也可稱之為B借用了A。
引用按照數(shù)據(jù)結(jié)構(gòu)可以分為兩類。

  • 簡單引用。其占用的大小與 usize 一致。
  • 切片。切片( &[T] )。 T 為切片內(nèi)元素的類型。切片類型存有: "地址(ptr) + 長度(len) "兩個字段。

智能指針

智能指針的概念起源于C++,智能指針是一類數(shù)據(jù)結(jié)構(gòu),他們的表現(xiàn)類似指針,但是擁有額外的元數(shù)據(jù)和功能。
在Rust中,引用和智能指針的一個的區(qū)別是引用是一類只借用數(shù)據(jù)的指針;相反,在大部分情況下,智能指針擁有他們指向的數(shù)據(jù)。Rust標準庫中不同的智能指針提供了比引用更豐富的功能:

  • Box<T>,用于在堆上分配數(shù)據(jù)。
  • Rc<T>,一個引用計數(shù)類型,其數(shù)據(jù)可以有多個所有者。
  • Ref<T> 和 RefMut<T>,通過RefCell<T>訪問,一個在運行時而不是在編譯時執(zhí)行借用規(guī)則的類型。
    每一個智能指針的實現(xiàn)都不大一樣,這里就不再展開敘述。
Box指針

在Rust中,所有值默認都是棧上分配。通過創(chuàng)建Box<T>,可以把值裝箱,使它在堆上分配。Box<T>類型是一個智能指針,因為它實現(xiàn)了Dereftrait,它允許Box<T>值被當作引用對待。當Box<T>值離開作用域時,由于它實現(xiàn)了Droptrait,首先刪除其指向的堆數(shù)據(jù),然后刪除自身。
Box<T>是堆上分配的指針類型,稱為“裝箱”(boxed),其指針本身在棧,指向的數(shù)據(jù)在堆,在Rust中提供了最簡單的堆分配類型。使用Box<T>的情況:

  • 遞歸類型和trait對象。Rust需要在編譯時知道一個類型占用多少空間,Box<T>的大小是已知的。
    例子如下(merkle.rs中的tree的定義):
/// Binary Tree where leaves hold a stand-alone value.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Tree<T> {
    Empty {
        hash: Vec<u8>,
    },

    Leaf {
        hash: Vec<u8>,
        value: T,
    },

    Node {
        hash: Vec<u8>,
        left: Box<Tree<T>>,
        right: Box<Tree<T>>,
    },
}
Rc和Arc(具體遇到再詳看)

對一個資源,同一時刻,有且只有一個所有權(quán)擁有者。Rc 和 Arc 使用引用計數(shù)的方法,讓程序在同一時刻,實現(xiàn)同一資源的多個所有權(quán)擁有者,多個擁有者共享資源。

Rc 用于同一線程內(nèi)部,通過 use std::rc::Rc 來引入。它有以下幾個特點:

  • 用 Rc 包裝起來的類型對象,是 immutable 的,即 不可變的。即你無法修改 Rc<T> 中的 T 對象,只能讀;
  • 一旦最后一個擁有者消失,則資源會被自動回收,這個生命周期是在編譯期就確定下來的;
  • Rc 只能用于同一線程內(nèi)部,不能用于線程之間的對象共享(不能跨線程傳遞);
  • Rc 實際上是一個指針,它不影響包裹對象的方法調(diào)用形式(即不存在先解開包裹再調(diào)用值這一說)。

Arc 是原子引用計數(shù),是 Rc 的多線程版本。Arc 通過 std::sync::Arc 引入。它的特點:

  • Arc 可跨線程傳遞,用于跨線程共享一個對象;
  • 用 Arc 包裹起來的類型對象,對可變性沒有要求;
  • 一旦最后一個擁有者消失,則資源會被自動回收,這個生命周期是在編譯期就確定下來的;
  • Arc 實際上是一個指針,它不影響包裹對象的方法調(diào)用形式(即不存在先解開包裹再調(diào)用值這一說);
  • Arc 對于多線程的共享狀態(tài)幾乎是必須的(減少復(fù)制,提高性能)。

Cell和RefCell

前面我們提到,Rust 通過其所有權(quán)機制,嚴格控制擁有和借用關(guān)系,來保證程序的安全,并且這種安全是在編譯期可計算、可預(yù)測的。但是這種嚴格的控制,有時也會帶來靈活性的喪失,有的場景下甚至還滿足不了需求。

因此,Rust 標準庫中,設(shè)計了這樣一個系統(tǒng)的組件:Cell, RefCell,它們彌補了 Rust 所有權(quán)機制在靈活性上和某些場景下的不足。

具體是因為,它們提供了 內(nèi)部可變性(相對于標準的 繼承可變性 來講的)。通常,我們要修改一個對象,必須

  • 成為它的擁有者,并且聲明 mut;
  • 或 以 &mut 的形式,借用;
    而通過 Cell, RefCell,我們可以在需要的時候,就可以修改里面的對象。而不受編譯期靜態(tài)借用規(guī)則束縛。

Cell 有如下特點:

  • Cell<T> 只能用于 T 實現(xiàn)了 Copy 的情況;

相對于 Cell 只能包裹實現(xiàn)了 Copy 的類型,RefCell 用于更普遍的情況(其它情況都用 RefCell)。

相對于標準情況的 靜態(tài)借用,RefCell 實現(xiàn)了 運行時借用,這個借用是臨時的。這意味著,編譯器對 RefCell 中的內(nèi)容,不會做靜態(tài)借用檢查,也意味著,出了什么問題,用戶自己負責。

RefCell 的特點:

  • 在不確定一個對象是否實現(xiàn)了 Copy 時,直接選 RefCell;
  • 如果被包裹對象,同時被可變借用了兩次,則會導(dǎo)致線程崩潰。所以需要用戶自行判斷;
  • RefCell 只能用于線程內(nèi)部,不能跨線程;
  • RefCell 常常與 Rc 配合使用(都是單線程內(nèi)部使用);

Rc是不可變的,RefCell不去做靜態(tài)檢查,提供內(nèi)部可變性。他倆的組合:

use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
    shared_map.borrow_mut().insert("africa", 92388);
    shared_map.borrow_mut().insert("kyoto", 11837);
    shared_map.borrow_mut().insert("piccadilly", 11826);
    shared_map.borrow_mut().insert("marbles", 38);
}

如無必要,不要頻繁使用。

裸指針

類似 C 語言里面的指針,可以為 null !

創(chuàng)建裸指針,是 safe 的,讀寫裸指針,才需要 unsafe !

裸指針又可以分為可變、不可變,分別寫作 *mut T 和 *const T

這里的星號不是解引用運算符,它是類型名稱的一部分。

這里的 T 表示指針指向的具體類型,裸指針本身的的類型大小與 usize 一致。

它允許別名,允許用來寫共享所有權(quán)的類型,甚至是內(nèi)存安全的共享內(nèi)存類型如:Rc<T>和Arc<T>,但是賦予你更多權(quán)利的同時意味著你需要擔當更多的責任:

  • 不能保證指向有效的內(nèi)存,甚至不能保證是非空的
  • 是普通舊式類型,也就是說,它不移動所有權(quán),因此Rust編譯器不能保證不出像釋放后使用這種bug
  • 缺少任何形式的生命周期,不像&,因此編譯器不能判斷出懸垂指針
  • 除了不允許直接通過*const T改變外,沒有別名或可變性的保障
    裸指針使用范例:
struct Student {
    name: String,
    age: i32,
}
fn main() {

    let a = 1;
    let b = &a as *const i32;

    let mut x = 2;
    let y = &mut x as *mut i32;

    unsafe{
        println!("{:?}",*y);
        *y = *y + 5;
        println!("{:?}",*y);
    }
}

如無必要,盡可能少使用裸指針,引入不安全因素。

8. 模式

在rust中,match有兩個主要作用,一是相當于其它語言的switch語句進行模式匹配,另一點是解構(gòu)數(shù)據(jù)。

模式匹配
enum Direction {
    East,
    West,
    North,
    South,
}

fn main() {
    let dire = Direction::South;
    match dire {
        Direction::East => println!("East"),
        Direction::North | Direction::South => {
            println!("South or North");
        },
        _ => println!("West"),
    };
}

這是一個沒什么實際意義的程序,但是能清楚的表達出match的用法??吹竭@里,你肯定能想起一個常見的控制語句——switch。沒錯,match可以起到和switch相同的作用。不過有幾點需要注意:

  • match所羅列的匹配,必須窮舉出其所有可能。當然,你也可以用 _ 這個符號來代表其余的所有可能性情況,就類似于switch中的default語句。
  • match的每一個分支都必須是一個表達式,并且,除非一個分支一定會觸發(fā)panic,這些分支的所有表達式的最終返回值類型必須相同??梢园裮atch整體視為一個表達式,既然是一個表達式,那么就一定能求得它的結(jié)果。
解構(gòu)數(shù)據(jù)

match還有一個非常重要的作用就是對現(xiàn)有的數(shù)據(jù)結(jié)構(gòu)進行解構(gòu),輕易的可以拿出其中的數(shù)據(jù)部分來。

enum Action {
    Say(String),
    MoveTo(i32, i32),
    ChangeColorRGB(u16, u16, u16),
}

fn main() {
    let action = Action::Say("Hello Rust".to_string());
    match action {
        Action::Say(s) => {
            println!("{}", s);
        },
        Action::MoveTo(x, y) => {
            println!("point from (0, 0) move to ({}, {})", x, y);
        },
        Action::ChangeColorRGB(r, g, _) => {
            println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
                r, g,
            );
        }
    }
}
ref 和ref mut

前面我們了解到,當被模式匹配命中的時候,未實現(xiàn)Copy的類型會被默認的move掉,因此,原owner就不再持有其所有權(quán)。

但是有些時候,我們只想要從中拿到一個變量的(可變)引用,而不想將其move出作用域,怎么做呢?答:用ref或者ref mut。

fn main() {
    let mut x = String::from("match test");

    // It will show the following error:
    // borrow of moved value: `x`
    // match x{
    //     mr=> println!("{:?}",mr),
    // }
    // println!("{:?}",x);


    println!("{:?}", x);
    match x {
        ref mut mr => {
            println!("mut ref before:{}", mr);
            mr.push_str(" end");
            println!("mut ref end:{}", mr);
        },
    }
    println!("{:?}", x);

    // 當然了……在let表達式里也能用
    // let ref mut mrx = x;
}

9. FFI

FFI是用來與其它語言交互的接口,在有些語言里面稱為語言綁定(language bindings).Java 里面一般稱為 JNI(Java Native Interface) 或 JNA(Java Native Access)。

由于現(xiàn)實中很多程序是由不同編程語言寫的,必然會涉及到跨語言調(diào)用,比如 A 語言寫的函數(shù)如果想在 B 語言里面調(diào)用,這時一般有兩種解決方案:一種是將函數(shù)做成一個服務(wù),通過進程間通信IPC或網(wǎng)絡(luò)協(xié)議通信RPC,RESTFUL等);另一種就是直接通過 FFI 調(diào)用。前者需要至少兩個獨立的進程才能實現(xiàn),而后者直接將其它語言的接口內(nèi)嵌到本語言中,所以調(diào)用效率比前者高。

如果在SGX開發(fā),由于要調(diào)用intel-sgx,所以必須了解ffi基本知識。

ffi分為兩種,一種是調(diào)用其它語言的接口,一種是制作語言接口供其它語言調(diào)用。

rust調(diào)用c語言

rust調(diào)用c語言主要分為以下四個過程:

  • 引入libc庫和c庫
  • 聲明你的ffi函數(shù),聲明調(diào)用那個c函數(shù)
  • 調(diào)用聲明的ffi函數(shù)
  • 將調(diào)用函數(shù)封裝為unsafe,暴露安全接口
引入libc庫

由于cffi的數(shù)據(jù)類型與rust不完全相同,我們需要引入libc庫來表達對應(yīng)ffi函數(shù)中的類型。

在Cargo.toml中添加以下行:

[dependencies]
libc = "0.2.9"

在你的rs文件中引入庫:

extern crate libc
聲明你的ffi函數(shù),聲明調(diào)用哪個c函數(shù)

就像c語言需要#include聲明了對應(yīng)函數(shù)的頭文件一樣,rust中調(diào)用ffi也需要對對應(yīng)函數(shù)進行聲明。

use libc::c_int;
use libc::c_void;
use libc::size_t;

#[link(name = "yourlib")]
extern {
    fn your_func(arg1: c_int, arg2: *mut c_void) -> size_t; // 聲明ffi函數(shù)
    fn your_func2(arg1: c_int, arg2: *mut c_void) -> size_t;
    static ffi_global: c_int; // 聲明ffi全局變量
}

聲明一個ffi庫需要一個標記有#[link(name = "yourlib")]的extern塊。name為對應(yīng)的庫(so/dll/dylib/a)的名字。 如:如果你需要snappy庫(libsnappy.so/libsnappy.dll/libsnappy.dylib/libsnappy.a), 則對應(yīng)的name為snappy。 在一個extern塊中你可以聲明任意多的函數(shù)和變量。

調(diào)用聲明的3ffi函數(shù)

聲明完成后就可以進行調(diào)用了。 由于此函數(shù)來自外部的c庫,所以rust并不能保證該函數(shù)的安全性。因此,調(diào)用任何一個ffi函數(shù)需要一個unsafe塊。

let result: size_t = unsafe {
    your_func(1 as c_int, Box::into_raw(Box::new(3)) as *mut c_void)
};
封裝unsafe,暴露安全接口

作為一個庫作者,對外暴露不安全接口是一種非常不合格的做法。在做c庫的rust binding時,我們做的最多的將是將不安全的c接口封裝成一個安全接口。 通常做法是:在一個叫ffi.rs之類的文件中寫上所有的extern塊用以聲明ffi函數(shù)。在一個叫wrapper.rs之類的文件中進行包裝:

// ffi.rs(ffi 聲明)
#[link(name = "yourlib")]
extern {
    fn your_func(arg1: c_int, arg2: *mut c_void) -> size_t;
}

wrapper如下:

// wrapper.rs
fn your_func_wrapper(arg1: i32, arg2: &mut i32) -> isize {
    unsafe { your_func(1 as c_int, Box::into_raw(Box::new(3)) as *mut c_void) } as isize
}

更多細節(jié)查看:rust調(diào)用ffi函數(shù)

其他語言調(diào)用rust語言編譯的庫

為了能讓rust的函數(shù)通過ffi被調(diào)用,需要加上extern "C"對函數(shù)進行修飾。

但由于rust支持重載,所以函數(shù)名會被編譯器進行混淆,就像c++一樣。因此當你的函數(shù)被編譯完畢后,函數(shù)名會帶上一串表明函數(shù)簽名的字符串。

比如:fn test() {}會變成_ZN4test20hf06ae59e934e5641haaE. 這樣的函數(shù)名為ffi調(diào)用帶來了困難,因此,rust提供了#[no_mangle]屬性為函數(shù)修飾。 對于帶有#[no_mangle]屬性的函數(shù),rust編譯器不會為它進行函數(shù)名混淆。如:

demo:


#[derive(Debug)]
struct Foo<T> {
  t: T
}

#[no_mangle]
extern "C" fn new_foo_vec() -> *const c_void {
    Box::into_raw(Box::new(Foo {t: vec![1,2,3]})) as *const c_void
}

將上述的代碼編譯成相應(yīng)的lib,就可以被其他語言調(diào)用。

具體詳查:將Rust編譯成庫

10.多線程編程模型,鎖等(在SGX的開發(fā)中基本不會用到)

后面再根據(jù)需要進行補充。

  • 線程(thread::spawn)
  • 消息傳遞(channel 推薦)
  • 共享內(nèi)存
  • 同步(主要是原子鎖,智能指針Mutex,RWLock)
  • 與并發(fā)相關(guān)的trait。(send和sync)
  • 智能指針Arc在并發(fā)中的使用。

11.作用域

關(guān)于借用的規(guī)則,在RustPrimer中有做以下的說明:

- 同一時刻,最多只有一個可變借用(&mut T),或者2。
- 同一時刻,可有0個或多個不可變借用(&T)但不能有任何可變借用。
- 借用在離開作用域后釋放。
- 在可變借用釋放前不可訪問源變量。

下面舉兩個例子說明其中的疑惑之處:

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

fn main() {
    let mut student = Student {
        age: 12,
        name: String::from("jojo"),
    };

    let ptr = &mut student;
    ptr.age = 3;
    println!("student ptr: {:?}", ptr);

    let ptr2 = &mut student;
    ptr2.age = 5;
    println!("student ptr2: {:?}", ptr2);

    println!("student struct: {:?}", student);
}

上述程序編譯運行沒有問題,運行結(jié)果:

student ptr: Student { age: 3, name: "jojo" }
student ptr2: Student { age: 5, name: "jojo" }
student struct: Student { age: 5, name: "jojo" }

下面一段程序會編譯報錯:

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

fn main() {
    let mut student = Student {
        age: 12,
        name: String::from("jojo"),
    };

    let ptr = &mut student;
    let ptr2 = &mut student;


    ptr.age = 3;
    println!("student ptr: {:?}", ptr);

    ptr2.age = 5;
    println!("student ptr2: {:?}", ptr2);

    println!("student struct: {:?}", student);
}

運行結(jié)果:

error[E0499]: cannot borrow `student` as mutable more than once at a time
  --> src/main.rs:14:16
   |
13 |     let ptr = &mut student;
   |               ------------ first mutable borrow occurs here
14 |     let ptr2 = &mut student;
   |                ^^^^^^^^^^^^ second mutable borrow occurs here
...
17 |     ptr.age = 3;
   |     ----------- first borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.
error: could not compile `high_level_trait`.

因為一行語句的位置的移動,導(dǎo)致無法編譯通過。
對于rust的作用域所有權(quán)規(guī)則中的同一刻,還需要之后再研究一下。

參考文檔:
https://legacy.gitbook.com/book/rustcc/rustprimer

https://kaisery.gitbooks.io/trpl-zh-cn/content/ch19-03-advanced-traits.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容