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)的類型實例。

例子中的代碼,如下:
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