語法和語義
變量綁定
變量綁定默認是不可變的
let可以使用模式
let (x, y) = (1, 2);
類型推斷
let x: i32 = 5;
變量使用之前必須初始化
變量作用域僅在一個被{}包圍的語句塊中
變量可以被隱藏。這意味着一個後聲明的並位於同一作用域的相同名字的變量綁定將會覆蓋前一個變量綁定
函數
函數參數需要聲明類型
Rust 函數只能返回一個值,並且你需要在一個“箭頭”後面聲明類型,它是一個破折號(-)後跟一個大於號(>)
表達式結尾不加分號,語句結尾加分號
表達式返回一個值而語句不是,語句返回一個空元組
rust中,使用return是一個糟糕的風格
發散函數並不返回,使用->!來代表發散函數
發散函數可以被用作任何類型
let f: fn(i32) -> i32;
f是一個指向一個獲取i32作爲參數並返回i32的函數的變量綁定。
可以用f來調用這個函數
原生類型
bool char 數字類型
數組的類型是[T; N]。我們會在泛型部分的時候討論這個T標記。N是一個編譯時常量,代表數組的長度。
有一個可以將數組中每一個元素初始化爲相同值的簡寫。在這個例子中,a的每個元素都被初始化爲0:
let a = [0; 20]; // a: [i32; 20]
切片
你可以用一個&和[]的組合從多種數據類型創建一個切片。&表明切片類似於引用,這個我們會在本部分的後面詳細介紹。帶有一個範圍的[],允許你定義切片的長度:
片段擁有&[T]類型。let a = [0, 1, 2, 3, 4];
let complete = &a[..]; // A slice containing all of the elements in alet middle = &a[1..4]; // A slice of a: just the elements 1, 2, and 3
str 原始字符串,不定長類型
元組
let (x, y, z) = (1, 2, 3);
可以使用let來解構元組
你可以一個逗號來消除一個單元素元組和一個括號中的值的歧義:
(0,); // single-element tuple
(0); // zero in parentheses
註釋
// 行註釋
/// 文檔註釋
//! 包含註釋
if語句
if不需要用括號
循環
loop 死循環
while
for var in expression {
code
}
所有權
ownership
Rust 中的變量綁定有一個屬性:它們有它們所綁定的的值的所有權。這意味着當一個綁定離開作用域,它們綁定的資源就會被釋放
*move semantics
Rust 確保了對於任何給定的資源都正好(只)有一個綁定與之對應,我們把所有權轉移給別的別的綁定時,我們說我們“移動”了我們引用的值。
*copy 類型*
我們已經知道了當所有權被轉移給另一個綁定以後,你不能再使用原始綁定。然而,有一個trait會改變這個行爲,它叫做Copy。
一個i32,它實現了Copy。這意味着,就像一個移動,當我們把v賦值給v2,產生了一個數據的拷貝。不過,不像一個移動,我們仍可以在之後使用v。這是因爲i32並沒有指向其它數據的指針,對它的拷貝是一個完整的拷貝。
所有基本類型都實現了Copy trait,因此他們的所有權並不像你想象的那樣遵循“所有權規則”被移動。
所有權之外
引用和借用
借用
與其直接傳遞v1和v2,我們傳遞&v1和&v2。我們稱&T類型爲一個”引用“,而與其擁有這個資源,它借用了所有權。一個借用變量的綁定在它離開作用域時並不釋放資源。
&mut引用
&mut T。一個“可變引用”允許你改變你借用的資源,一個&mut引用。你也需要使用他們(星號)來訪問引用的內容。
規則
第一,任何借用必須位於比擁有者更小的作用域。第二,同一個作用域下,不能同時存在可變和不可變的引用:
0 個或 N 個資源的引用(&T)
只有 1 個可變引用(&mut T)
釋放後使用
引用必須與它引用的值存活得一樣長。Rust 會檢查你的引用的作用域來保證這是正確的。
生命週期
Rust 所有權系統通過一個叫生命週期(lifetime)的概念來做到這一點,它定義了一個引用有效的作用域。
'a讀作“生命週期 a”。技術上講,每一個引用都有一些與之相關的生命週期,不過編譯器在通常情況讓你可以省略(也就是,省略,查看下面的生命週期省略)它們。
static
叫做static的作用域是特殊的。它代表某樣東西具有橫跨整個程序的生命週期。大部分 Rust 程序員當他們處理字符串時第一次遇到'static
生命週期省略
有3條規則:
每一個被省略的函數參數成爲一個不同的生命週期參數。
如果剛好有一個輸入生命週期,不管是否省略,這個生命週期被賦予所有函數返回值中被省略的生命週期。
如果有多個輸入生命週期,不過它們當中有一個是&self或者&mut self,self的生命週期被賦予所有省略的輸出生命週期。
否則,省略一個輸出生命週期將是一個錯誤。
可變性
結構體
更新語法
一個包含..的struct表明你想要使用一些其它結構體的拷貝的一些值。例如:
struct Point3d {
x: i32,
y: i32,
z: i32,
}
let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point };
元組結構體
Rust有像另一個元組和結構體的混合體的數據類型。元組結構體有一個名字,不過它的字段沒有。他們用struct關鍵字聲明,並元組前面帶有一個名字:
不過有種情況元組結構體非常有用,就是當元組結構體只有一個元素時。我們管它叫新類型(newtype),因爲你創建了一個與元素相似的類型:
struct Inches(i32);
let length = Inches(10);
let Inches(integer_length) = length;
println!("length is {} inches", integer_length);
如你所見,你可以通過一個解構let來提取內部的整型,就像我們在講元組時說的那樣,let Inches(integer_length)給integer_length賦值爲10。
類單元結構體
這樣的結構體叫做“類單元”因爲它與一個空元組類似,(),這有時叫做“單元”。就像一個元組結構體,
它定義了一個新類型。就它本身來看沒什麼用(雖然有時它可以作爲一個標記類型),不過在與其它功能的結合中,它可以變得有用。
例如,一個庫可能請求你創建一個實現了一個特定特性的結構來處理事件。如果你並不需要在結構中存儲任何數據,
你可以僅僅創建一個類單元結構體。
枚舉
Rust 中的一個enum是一個代表數個可能變量的數據的類型。每個變量都可選是否關聯數據:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
定義變量的語法與用來定義結構體的語法類似:你可以有不帶數據的變量(像類單元結構體),帶有命名數據的變量,和帶有未命名數據的變量(像元組結構體)。然而,不像單獨的結構體定義,一個enum是一個單獨的類型。一個枚舉的值可以匹配任何一個變量。因爲這個原因,枚舉有時被叫做“集合類型”:枚舉可能值的集合是每一個變量可能值的集合的總和。
匹配
match
match強制窮盡性檢查,所有分支必須都做處理
match也是一個表達式,也就是說它可以用在let綁定的右側或者其它直接用到表達式的地方
匹配枚舉
match的另一個重要的作用是處理枚舉的可能變量:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
fn quit() { }
fn change_color(r: i32, g: i32, b: i32) { }
fn move_cursor(x: i32, y: i32) {}
fn process_message(msg: Message) {
match msg {
Message::Quit => quit(),
Message::ChangeColor(r, g, b) => change_color(r, g, b),
Message::Move { x: x, y: y } => move_cursor(x, y),
Message::Write(s) => println!("{}", s),
};
}
模式
模式十分強大
記住幾個準則
match匹配的內容如果是常量或者枚舉時,是我們傳統意義理解的比較,其他的統統是表達式,是做的模式匹配。
方法語法
方法調用
self如果它只是棧上的一個值,&self如果它是一個引用,然後&mut self如果它是一個可變引用
我們應該默認使用&self,就像相比獲取所有權你應該更傾向於借用,同樣相比獲取可變引用更傾向於不可變引用一樣
鏈式方法調用
關聯函數
注意靜態函數是通過Struct::method()語法調用的,而不是ref.method()語法。
創建者模式
vectors
對於重複初始值有另一種形式的vec!:
let v = vec![0; 10]; // ten zeroes
另外值得注意的是必須用usize類型的值來索引
如果嘗試訪問並不存在的索引,麼當前的線程會 panic並輸出信息,如果你想處理越界錯誤而不是 panic,你可以使用像get
或get_mut
這樣的方法,他們當給出一個無效的索引時返回None:
let v = vec![1, 2, 3];
match v.get(7) {
Some(x) => println!("Item 7 is {}", x),
None => println!("Sorry, this vector is too short.")
}
迭代
可以用for來迭代 vector 的元素,迭代有三個版本,&,&mut,take ownership
字符串
一個字符串是一串UTF-8字節編碼的Unicode量級值的序列。所有的字符串都確保是有效編碼的UTF-8序列。另外,字符串並不以null結尾並且可以包含null字節
泛型
fn takes_anything<T>(x: T) {
// do something with x
}
函數泛型
fn takes_anything<T>(x: T) {
// do something with x
}
結構體泛型
struct Point<T> {
x: T,
y: T,
}
traits
trait HasArea {
fn area(&self) -> f64;
}
fn print_area<T: HasArea>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
語法是指any type that implements the HasArea trait(任何實現了HasAreatrait的類型)。
類似其他語言中的接口的概念
泛型結構體的trait綁定
impl<T: PartialEq> Rectangle<T> { ... }
實現trait的規則
第一是如果 trait 並不定義在你的作用域,它並不能實現。
這還有一個實現trait的限制。不管是trait還是你寫的impl都只能在你自己的包裝箱內生效。
所以,我們可以爲i32實現HasAreatrait,因爲HasArea在我們的包裝箱中。不過如果我們想爲i32實現Floattrait,它是由Rust提供的,則無法做到,因爲這個trait和類型都不在我們的包裝箱中。
多trait綁定
如果你需要多於1個限定,可以使用+:
use std::fmt::Debug;
fn foo<T: Clone + Debug>(x: T) {
x.clone();
println!("{:?}", x);
}
T現在需要實現Clone和Debug。
where從句
如果你需要多於1個限定,可以使用+:
use std::fmt::Debug;
fn foo<T: Clone + Debug>(x: T) {
x.clone();
println!("{:?}", x);
}
T現在需要實現Clone和Debug。
默認方法
trait Foo {
fn is_valid(&self) -> bool;
fn is_invalid(&self) -> bool { !self.is_valid() }
}
默認方法可以不實現,也可以覆蓋
impl Foo for OverrideDefault {
fn is_valid(&self) -> bool {
println!("Called OverrideDefault.is_valid.");
true
}
fn is_invalid(&self) -> bool {
println!("Called OverrideDefault.is_invalid!");
true // overrides the expected value of is_invalid()
}
}
繼承
有時,實現一個trait要求實現另一個trait:
// trait Foo {
// fn foo(&self);
// }
// trait FooBar : Foo {
// fn foobar(&self);
// }
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
deriving
重複的實現像Debug和Default這樣的 trait 會變得很無趣。爲此,Rust 提供了一個屬性來允許我們讓 Rust 爲我們自動實現 trait:
` #[derive(Debug)]struct Foo;
fn main() {
println!("{:?}", Foo);
}
然而,deriving 限制爲一些特定的 trait:
Clone
Copy
Debug
Default
Eq
Hash
Ord
PartialEq
PartialOrd
Drop
Drop trait提供了一個當一個值離開作用域後運行一些代碼的方法。例如:
struct HasDrop;
impl Drop for HasDrop {
fn drop(&mut self) {
println!("Dropping!");
}
}
fn main() {
let x = HasDrop;
// do stuff
} // x goes out of scope here
值會以與它們聲明相反的順序被丟棄(dropped)
Drop用來清理任何與struct關聯的資源
if let
if let允許你合併if和let來減少特定類型模式匹配的開銷
如果一個模式匹配成功,它綁定任何值的合適的部分到模式的標識符中,並計算這個表達式。如果模式不匹配,啥也不會發生。
如果你想在模式不匹配時做點其他的,你可以使用else:
# let option = Some(5);
# fn foo(x: i32) { }
# fn bar() { }
if let Some(x) = option {
foo(x);
} else {
bar();
}
while let
類似的,當你想一直循環,直到一個值匹配到特定的模式的時候,你可以選擇使用while let。使用while let可以把類似這樣的代碼:
let mut v = vec![1, 3, 5, 7, 11];
loop {
match v.pop() {
Some(x) => println!("{}", x),
None => break,
}
}
變成這樣的代碼:
let mut v = vec![1, 3, 5, 7, 11];
while let Some(x) = v.pop() {
println!("{}", x);
}
trait 對象
當涉及到多態的代碼時,我們需要一個機制來決定哪個具體的版本應該得到執行。這叫做“分發”(dispatch)。大體上有兩種形式的分發:靜態分發和動態分發。雖然 Rust 喜歡靜態分發,不過它也提供了一個叫做“trait 對象”的機制來支持動態分發。
靜態分發
//# trait Foo { fn method(&self) -> String; }
//# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
//# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
fn do_something<T: Foo>(x: T) {
x.method();
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something(x);
do_something(y);
}
靜態分發能提高程序的運行效率,不過相應的也有它的弊端:會導致“代碼膨脹”(code bloat)。因爲在編譯出的二進制程序中,同樣的函數,對於每個類型都會有不同的拷貝存在。
編譯器也不是完美的並且“優化”後的代碼可能更慢。例如,過度的函數內聯會導致指令緩存膨脹(緩存控制着我們周圍的一切)。這也是爲何要謹慎使用#[inline]和#[inline(always)]的部分原因。另外一個使用動態分發的原因是,在一些情況下,動態分發更有效率。
然而,常規情況下靜態分發更有效率,並且我們總是可以寫一個小的靜態分發的封裝函數來進行動態分發,不過反過來不行,這就是說靜態調用更加靈活。因爲這個原因標準庫儘可能的使用了靜態分發。
動態分發
Rust 通過一個叫做“trait 對象”的功能提供動態分發。比如說&Foo、Box這些就是trait對象。它們是一些值,值中儲存實現了特定 trait 的任意類型。它的具體類型只能在運行時才能確定。
從一些實現了特定trait的類型的指針中,可以從通過轉型(casting)(例如,&x as &Foo)或者強制轉型(coercing it)(例如,把&x當做參數傳遞給一個接收&Foo類型的函數)來取得trait對象。
這些 trait 對象的強制多態和轉型也適用於類似於&mut Foo的&mut T以及Box的Box這樣的指針,也就是目前爲止我們討論到的所有指針。強制轉型和轉型是一樣的。
這個操作可以被看作“清除”編譯器關於特定類型指針的信息,因此trait對象有時被稱爲“類型清除”(type erasure)。
//# trait Foo { fn method(&self) -> String; }
//# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
//# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
fn do_something(x: &Foo) {
x.method();
}
fn main() {
let x = 5u8;
do_something(&x as &Foo);//或者do_something(&x);
}
一個使用trait對象的函數並沒有爲每個實現了Foo的類型專門生成函數:它只有一份函數的代碼,一般(但不總是)會減少代碼膨脹。然而,因爲調用虛函數,會帶來更大的運行時開銷,也會大大地阻止任何內聯以及相關優化的進行。
表現(representation)
類似c++虛表
對象安全
並不是所有 trait 都可以被用來作爲一個 trait 對象只有對象安全的 trait 才能成爲 trait 對象。一個對象安全的 trait 需要如下兩條爲真:
trait 並不要求Self: Sized
所有的方法是對象安全的
那麼什麼讓一個方法是對象安全的呢?每一個方法必須要求Self: Sized或者如下所有:
必須沒有任何類型參數
必須不使用Self
幾乎所有的規則都談到了Self。一個直觀的理解是“除了特殊情況,如果你的 trait 的方法使用了Self,它就不是對象安全的”。
閉包
語法
有時爲了整潔和複用打包一個函數和自由變量(free variables)是很有用的。自由變量是指被用在函數中來自函數內部作用域並只用於函數內部的變量。
記住{}是一個表達式,所以我們也可以擁有包含多行的閉包
閉包及環境
之所以把它稱爲“閉包”是因爲它們“包含在環境中”(close over their environment)。這看起來像:
let num = 5;
let plus_num = |x: i32| x + num;
assert_eq!(10, plus_num(5));
這個閉包,plus_num,引用了它作用域中的let綁定:num。更明確的說,它借用了綁定。如果我們做一些會與這個綁定衝突的事,我們會得到一個錯誤。比如這個:
+
let mut num = 5;
let plus_num = |x: i32| x + num;
let y = &mut num;
然而,如果你的閉包需要它,Rust會取得所有權並移動環境。這個不能工作:
let nums = vec![1, 2, 3];
let takes_nums = || nums;
println!("{:?}", nums);
move閉包
let mut num = 5;
{
let mut add_num = move |x: i32| num += x;
add_num(5);
}
assert_eq!(5, num);
我們只會得到5。與其獲取一個我們num的可變借用,我們取得了一個拷貝的所有權。
另一個理解move閉包的方法:它給出了一個擁有自己棧幀的閉包。沒有move,一個閉包可能會綁定在創建它的棧幀上,而move閉包則是獨立的。例如,這意味着大體上你不能從函數返回一個非move閉包。
閉包的實現
閉包作爲參數
靜態分發
fn call_with_one(some_closure: F) -> i32where F : Fn(i32) -> i32 {
some_closure(1)
}
let answer = call_with_one(|x| x + 2);
assert_eq!(3, answer);
動態分發
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(1)
}
let answer = call_with_one(&|x| x + 2);
assert_eq!(3, answer);
函數指針和閉包
一個函數指針有點像一個沒有環境的閉包。因此,你可以傳遞函數指針給任何期待閉包參數的函數,且能夠工作:
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(1)
}
fn add_one(i: i32) -> i32 {
i + 1
}
let f = add_one;
let answer = call_with_one(&f);
assert_eq!(2, answer);
返回閉包
fn factory() -> Box<Fn(i32) -> i32> {
let num = 5;
Box::new(move |x| x + num)
}
# fn main() {
let f = factory();
let answer = f(1);
assert_eq!(6, answer);
# }
通用函數調用語法
我們需要一個區分我們需要調用哪一函數的方法。這個功能叫做“通用函數調用語法”
# trait Foo {
# fn f(&self);
# }
# trait Bar {
# fn f(&self);
# }
# struct Baz;
# impl Foo for Baz {
# fn f(&self) { println!("Baz’s impl of Foo"); }
# }
# impl Bar for Baz {
# fn f(&self) { println!("Baz’s impl of Bar"); }
# }
# let b = Baz;
Foo::f(&b);
Bar::f(&b);
尖括號形式
<Type as Trait>::method(args);
<>::語法是一個提供類型提示的方法。類型位於<>中。在這個例子中,類型是Type as Trait,表示我們想要method的Trait版本被調用。在沒有二義時as Trait部分是可選的。尖括號也是一樣。因此上面的形式就是一種縮寫的形式。
trait Foo {
fn foo() -> i32;
}
struct Bar;
impl Bar {
fn foo() -> i32 {
20
}
}
impl Foo for Bar {
fn foo() -> i32 {
10
}
}
fn main() {
assert_eq!(10, <Bar as Foo>::foo());
assert_eq!(20, Bar::foo());
}
使用尖括號語法讓你可以調用指定trait的方法而不是繼承到的那個
crate和模塊
包裝箱和模塊
包裝箱是其它語言中庫(library)或包(package)的同義詞。因此“Cargo”則是Rust包管理工具的名字:你通過Cargo把你當包裝箱交付給別人。包裝箱可以根據項目的不同生成可執行文件或庫文件。
每個包裝箱有一個隱含的根模塊(root module)包含模塊的代碼。你可以在根模塊下定義一個子模塊樹。模塊允許你爲自己模塊的代碼分區。
+-----------+
+---| greetings |
| +-----------+
+---------+ |
+---| english |---+
| +---------+ | +-----------+
| +---| farewells |
+---------+ | +-----------+
| phrases |---+
+---------+ | +-----------+
| +---| greetings |
| +----------+ | +-----------+
+---| japanese |--+
+----------+ |
| +-----------+
+---| farewells |
+-----------+
定義模塊
我們用mod關鍵字來定義我們的每一個模塊。讓我們把src/lib.rs寫成這樣:
mod english {
mod greetings {
}
mod farewells {
}
}
mod japanese {
mod greetings {
}
mod farewells {
}
}
多文件包裝箱
$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── src
│ ├── english
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ ├── japanese
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ └── lib.rs
└── target
└── debug
├── build
├── deps
├── examples
├── libphrases-a7448e02a0468eaa.rlib
└── native
src/lib.rs是我們包裝箱的根,它看起來像這樣
mod english;
mod japanese;
src/english/mod.rs和src/japanese/mod.rs都看起來像這樣:
mod greetings;
mod farewells;
在src/english/greetings.rs添加如下:
fn hello() -> String {
"Hello!".to_string()
}
導入外部包裝箱
extern crate phrases;
到處公用接口
Rust允許你嚴格的控制你的接口哪部分是公有的,所以它們默認都是私有的。你需要使用pub關鍵字,來公開它。
在我們的src/lib.rs,讓我們給english模塊聲明添加一個pub:
pub mod english;
mod japanese;
然後在我們的src/english/mod.rs中,加上兩個pub:
pub mod greetings;
pub mod farewells;
在我們的src/english/greetings.rs中,讓我們在fn聲明中加上pub:
pub fn hello() -> String {
"Hello!".to_string()
}
用use導入包裝箱
使用use之前我們需要:
extern crate phrases;
fn main() {
println!("Hello in English: {}", phrases::english::greetings::hello());
println!("Goodbye in English: {}", phrases::english::farewells::goodbye());
}
通過use我們可以簡化代碼
extern crate phrases;
use phrases::english::greetings;
use phrases::english::farewells;
fn main() {
println!("Hello in English: {}", greetings::hello());
println!("Goodbye in English: {}", farewells::goodbye());
}
導入模塊而不是直接導入函數被認爲是一個最佳實踐
public use重導出
修改你的src/japanese/mod.rs爲這樣:
pub use self::greetings::hello;
pub use self::farewells::goodbye;
mod greetings;
mod farewells;
然後我們可以將引用該包裝箱的代碼修改爲如下
extern crate phrases;
use phrases::english::{greetings,farewells};
use phrases::japanese;
fn main() {
println!("Hello in English: {}", greetings::hello());
println!("Goodbye in English: {}", farewells::goodbye());
println!("Hello in Japanese: {}", japanese::hello());
println!("Goodbye in Japanese: {}", japanese::goodbye());
}
pub use聲明將這些函數導入到了我們模塊結構空間中。因爲我們在japanese模塊內使用了pub use,我們現在有了phrases::japanese::hello()和phrases::japanese::goodbye()函數,即使它們的代碼在phrases::japanese::greetings::hello()和phrases::japanese::farewells::goodbye()函數中。內部結構並不反映外部接口。
複雜的導入
extern crate phrases as sayings;
use sayings::japanese::greetings as ja_greetings;
use sayings::japanese::farewells::*;
use sayings::english::{self, greetings as en_greetings, farewells as en_farewells};
fn main() {
println!("Hello in English; {}", en_greetings::hello());
println!("And in Japanese: {}", ja_greetings::hello());
println!("Goodbye in English: {}", english::farewells::goodbye());
println!("Again: {}", en_farewells::goodbye());
println!("And in Japanese: {}", goodbye());
}
const和static
const
常量貫穿於整個程序的生命週期。更具體的,Rust中的常量並沒有固定的內存地址。這是因爲實際上它們會被內聯到用到它們的地方。爲此對同一常量的引用並不能保證引用到相同的內存地址。
const N: i32 = 5;
static
Rust以靜態量的方式提供了類似“全局變量”的功能。它們與常量類似,不過靜態量在使用時並不內聯。這意味着對每一個值只有一個實例,並且位於內存中的固定位置。
static N: i32 = 5;
可變性
static mut N: i32 = 5;
因爲這是可變的,一個線程可能在更新N同時另一個在讀取它,導致內存不安全。因此訪問和改變一個static mut是不安全(unsafe)的,因此必須在unsafe塊中操作:
# static mut N: i32 = 5;
unsafe {
N += 1;
println!("N: {}", N);
}
更進一步,任何存儲在static的類型必須實現Sync。
初始化
const和static都要求賦予它們一個值。它們只能被賦予一個常量表達式的值。換句話說,你不能用一個函數調用的返回值或任何相似的複合值或在運行時賦值。
屬性
#[foo]struct Foo;
mod bar {
#![bar]
}
'''#[foo]作用於下一個項,在這就是struct聲明。#![bar]作用於包含它的項,在這是mod聲明。否則,它們是一樣的。它們都以某種方式改變它們附加到的項的意義。'''
'type'別名
type關鍵字讓你定義另一個類型的別名:
type Name = String;
然而要注意的是,這一個別名,完全不是一個新的類型。換句話說,因爲Rust是強類型的,你可以預期兩個不同類型的比較會失敗:
type Num = i32;
let x: i32 = 5;
let y: Num = 5;
if x == y {
// ...
}
這個比較不會失敗
類型轉換
提供了兩種不同的在不同類型間轉換的方式。第一個,as,用於安全轉換。相反,transmute允許任意的轉換,而這是 Rust 中最危險的功能之一!
關聯類型
定義關聯類型
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec<Self::E>;
}
十分簡單。關聯類型使用type關鍵字,並出現在trait體和函數中
實現關聯類型
# trait Graph {
# type N;
# type E;
# fn has_edge(&self, &Self::N, &Self::N) -> bool;
# fn edges(&self, &Self::N) -> Vec<Self::E>;
# }
struct Node;
struct Edge;
struct MyGraph;
impl Graph for MyGraph {
type N = Node;
type E = Edge;
fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
true
}
fn edges(&self, n: &Node) -> Vec<Edge> {
Vec::new()
}
}
trait對象和關聯類型
# trait Graph {
# type N;
# type E;
# fn has_edge(&self, &Self::N, &Self::N) -> bool;
# fn edges(&self, &Self::N) -> Vec<Self::E>;
# }
# struct Node;
# struct Edge;
# struct MyGraph;
# impl Graph for MyGraph {
# type N = Node;
# type E = Edge;
# fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
# true
# }
# fn edges(&self, n: &Node) -> Vec<Edge> {
# Vec::new()
# }
# }
let graph = MyGraph;
let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;
不定長類型
限制
我們只能通過指針操作一個不定長類型的實例。&[T]剛好能正常工作,不過[T]不行。一個&[T]能正常工作,不過一個[T]不行。
變量和參數不能擁有動態大小類型。
只有一個struct的最後一個字段可能擁有一個動態大小類型;其它字段則不可以擁有動態大小類型。枚舉變量不可以用動態大小類型作爲數據。
如果你想要寫一個接受動態大小類型的函數,你可以使用這個特殊的限制,?Sized:
struct Foo<T: ?Sized> {
f: T,
}
運算符重載
Rust 允許有限形式的運算符重載。特定的運算符可以被重載。要支持一個類型間特定的運算符,你可以實現一個的特定的重載運算符的trait。
use std::ops::Add;
#[derive(Debug)]struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
fn main() {
let p1 = Point { x: 1, y: 0 };
let p2 = Point { x: 2, y: 3 };
let p3 = p1 + p2;
println!("{:?}", p3);
}
有一系列可以這樣被重載的運算符,並且所有與之相關的trait都在std::ops
模塊中。查看它的文檔來獲取完整的列表
在泛型結構體中使用運算符trait
use std::ops::Mul;
trait HasArea<T> {
fn area(&self) -> T;
}
struct Square<T> {
x: T,
y: T,
side: T,
}
impl<T> HasArea<T> for Square<T>
where T: Mul<Output=T> + Copy {
fn area(&self) -> T {
self.side * self.side
}
}
fn main() {
let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 12.0f64,
};
println!("Area of s: {}", s.area());
}
'Deref'強制多態
標準庫提供了一個特殊的特性,Deref
。它一般用來重載*,解引用運算符:
use std::ops::Deref;
struct DerefExample<T> {
value: T,
}
impl<T> Deref for DerefExample<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
fn main() {
let x = DerefExample { value: 'a' };
assert_eq!('a', *x);
}
這對編寫自定義指針類型很有用。然而,有一個與Deref相關的語言功能:“解引用強制多態(deref coercions)”。規則如下:如果你有一個U類型,和它的實現Deref,(那麼)&U的值將會自動轉換爲&T。這是一個例子:
fn foo(s: &str) {
// borrow a string for a second
}
// String implements Deref<Target=str>let owned = "Hello".to_string();
// therefore, this works:
foo(&owned);
在一個值的前面用&號獲取它的引用。所以owned是一個String,&owned是一個&String,而因爲impl Deref for String,&String將會轉換爲&str,而它是foo()需要的。
標準庫提供的另一個非常通用的實現是:
fn foo(s: &[i32]) {
// borrow a slice for a second
}
// Vec<T> implements Deref<Target=[T]>let owned = vec![1, 2, 3];
foo(&owned);
向量可以Deref爲一個切片。
一個&&&&&&&&&&&&&&&&Foo類型的值仍然可以調用Foo定義的方法,因爲編譯器會插入足夠多的來使類型正確。而正因爲它插入,它用了Deref。
宏
定義宏
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
# fn main() {
# assert_eq!(vec![1,2,3], [1, 2, 3]);
# }
這就像一個match表達式分支,不過匹配發生在編譯時Rust的語法樹中。最後一個分支(這裏只有一個分支)的分號是可選的。=>左側的“模式”叫匹配器(matcher)。它有自己的語法。
匹配器將會匹配任何表達式,把它的語法樹綁定到元變量x:expr匹配器將會匹配任何Rust表達式,把它的語法樹綁定到元變量x上。expr標識符是一個片段分類符(fragment specifier)。在宏進階章節(已被本章合併,坐等官方文檔更新)中列舉了所有可能的分類符。匹配器寫在$(...)中,*會匹配0個或多個表達式,表達式之間用逗號分隔。
macro_rules! foo {
() => (let x = 3);
}
fn main() {
foo!();
println!("{}", x);
}
這對let綁定和loop標記有效,對items無效。所以下面的代碼可以編譯:上面的代碼無法編譯通過,而下面的則可以
macro_rules! foo {
() => (fn x() { });
}
fn main() {
foo!();
x();
}
裸指針
Rust 的標準庫中有一系列不同的智能指針類型,不過這有兩個類型是十分特殊的。Rust的安全大多來源於編譯時檢查,不過裸指針並沒有這樣的保證,使用它們是unsafe
的。
const T和mut T在Rust中被稱爲“裸指針”。
有一些你需要記住的裸指針不同於其它指針的地方。它們是:
不能保證指向有效的內存,甚至不能保證是非空的(不像Box和&);
沒有任何自動清除,不像Box,所以需要手動管理資源;
是普通舊式類型,也就是說,它不移動所有權,這又不像Box,因此Rust編譯器不能保證不出像釋放後使用這種bug;
被認爲是可發送的(如果它的內容是可發送的),因此編譯器不能提供幫助確保它的使用是線程安全的;例如,你可以從兩個線程中併發的訪問mut i32而不用同步。
缺少任何形式的生命週期,不像&,因此編譯器不能判斷出懸垂指針;
除了不允許直接通過const T改變外,沒有別名或可變性的保障。
創建一個裸指針是非常安全的:
let x = 5;
let raw = &x as *const i32;
let mut y = 10;
let raw_mut = &mut y as *mut i32;
當你解引用一個裸指針,你要爲它並不指向正確的地方負責。爲此,你需要unsafe:
let x = 5;
let raw = &x as *const i32;
let points_at = unsafe { *raw };
println!("raw points at {}", points_at);
在運行時,指向一份相同數據的裸指針和引用有相同的表現。事實上,在安全代碼中&T引用會隱式的轉換爲一個const T同時它們的mut變體也有類似的行爲(這兩種轉換都可以顯式執行,分別爲value as const T和value as mut T)。
反其道而行之,從const到&引用,是不安全的。一個&T總是有效的,所以,最少,const T裸指針必須指向一個T的有效實例。進一步,結果指針必須滿足引用的別名和可變性法則。編譯器假設這些屬性對任何引用都是有效的,不管它們是如何創建的,因而所以任何從裸指針來的轉換都斷言它們成立。程序員必須保證它。
推薦的轉換方法是
// explicit castlet i: u32 = 1;
let p_imm: *const u32 = &i as *const u32;
// implicit coercionlet mut m: u32 = 2;
let p_mut: *mut u32 = &mut m;
unsafe {
let ref_imm: &u32 = &*p_imm;
let ref_mut: &mut u32 = &mut *p_mut;
}
與使用transmute相比更傾向於&*x解引用風格。transmute遠比需要的強大,並且(解引用)更受限的操作會更難以錯誤使用;例如,它要求x是一個指針(不像transmute)。
不安全代碼
Rust主要魅力是它強大的靜態行爲保障。不過安全檢查天性保守:有些程序實際上是安全的,不過編譯器不能驗證它是否是真的。爲了寫這種類型的程序,我們需要告訴編譯器稍微放鬆它的限制。爲此,Rust有一個關鍵字,unsafe。使用unsafe的代碼比正常代碼有更少的限制。
unsafe fn danger_will_robinson() {
// scary stuff
}
unsafe {
// scary stuff
}
unsafe trait Scary { }
# unsafe trait Scary { }
unsafe impl Scary for i32 {}
在不安全函數和不安全塊,Rust將會讓你做3件通常你不能做的事:只有3件。它們是:
訪問和更新一個靜態可變變量
解引用一個裸指針
調用不安全函數。這是最NB的能力
高效rust
棧和堆
Box::::new() 堆
測試
test屬性
#[test]
執行
cargo test
會運行該屬性後面緊跟的函數
ignore屬性
#[test]
#[ignore]
只有通過
cargo test -- -- ignore
纔會運行ignore屬性的函數
tests模塊
#[cfg(test)]mod tests {
use super::*;
#[test]fn it_works() {
assert_eq!(4, add_two(2));
}
}
引入了一個cfg屬性的mod tests。這個模塊允許我們把所有測試集中到一起,並且需要的話還可以定義輔助函數,它們不會成爲我們包裝箱的一部分。cfg屬性只會在我們嘗試去運行測試時纔會編譯測試代碼。這樣可以節省編譯時間,並且也確保我們的測試代碼完全不會出現在我們的正式構建中。
tests目錄
爲了集成測試,我們創建了一個目錄tests,並加入lib.rs文件,內容
extern crate adder;
#[test]fn it_works() {
assert_eq!(4, adder::add_two(2));
}
我們現在有一行extern crate adder在開頭。這是因爲在tests目錄中的測試另一個完全不同的包裝箱,所以我們需要導入我們的庫。
文檔測試
這塊抽時間再仔細想想
條件編譯
Rust有一個特殊的屬性,#[cfg],它允許你基於一個傳遞給編譯器的標記編譯代碼。它有兩種形式:
#[cfg(foo)]
# fn foo() {}
#[cfg(bar = "baz")]
# fn bar() {}
它有一些輔助選項
#[cfg(any(unix, windows))]
# fn foo() {}
#[cfg(all(unix, target_pointer_width = "32"))]
# fn bar() {}
#[cfg(not(foo))]
# fn not_foo() {}
這些選項可以任意嵌套
#[cfg(any(not(unix), all(target_os="macos", target_arch = "powerpc")))]
# fn foo() {}
如果你使用Cargo的話,它們可以在你Cargo.toml中的[features]
部分設置:
[features]# no features by defaultdefault = []# The “secure-password” feature depends on the bcrypt package.secure-password = ["bcrypt"]
他等同於Cargo傳遞給rustc一個標記:
--cfg feature="${feature_name}"
cfg_attr
#[cfg_attr(a, b)]
# fn foo() {}
如果a通過cfg屬性設置了的話這與#[b]相同,否則不起作用。
cfg!
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
println!("Think Different!");
}
可以在代碼中使用這類標記
文檔
rustdoc
/// #
“#”後面的內容在通過註釋生成的測試代碼中會使用,但是生成的註釋文檔中不會顯示
迭代器
迭代器,迭代適配器,消費者
消費者
消費者 操作一個迭代器,返回一些值或者幾種類型的值。最常見的消費者是collect()
常見的消費者
let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
let greater_than_forty_two = (0..100)
.find(|x| *x > 42);
let sum = (1..4).fold(0, |sum, x| sum + x);
迭代器
迭代器是惰性的(lazy )並且不需要預先生成所有的值
範圍是你會見到的兩個基本迭代器之一。另一個是iter()。iter()可以把一個向量轉換爲一個簡單的按順序給出每個值的迭代器:
let nums = vec![1, 2, 3];
for num in nums.iter() {
println!("{}", num);
}
迭代適配器
代適配器(Iterator adapters)獲取一個迭代器然後按某種方法修改它,併產生一個新的迭代器。
(1..100).map(|x| x + 1);
在其他迭代器上調用map,然後產生一個新的迭代器,它的每個元素引用被調用了作爲參數的閉包。
因爲迭代器是惰性的,所以下面代碼不會打印任何內容
(1..100).map(|x| println!("{}", x));
有大量有趣的迭代適配器。take(n)會返回一個源迭代器下n個元素的新迭代器
filter()是一個帶有一個閉包參數的適配器。這個閉包返回true或false。filter()返回的新迭代器只包含閉包返回true的元素:
for i in (1..100).filter(|&x| x % 2 == 0) {
println!("{}", i);
}
鏈式結構
(1..)
.filter(|&x| x % 2 == 0)
.filter(|&x| x % 3 == 0)
.take(5)
.collect::<Vec<i32>>();
併發
send
當一個T類型實現了Send,它向編譯器指示這個類型的所有權可以在線程間安全的轉移。
我們有一個連接兩個線程的通道,我們想要能夠向通道發送些數據到另一個線程。因此,我們要確保這個類型實現了Send。
sync
當一個類型T實現了Sync,它向編譯器指示這個類型在多線程併發時沒有導致內存不安全的可能性。這隱含了沒有內部可變性的類型天生是Sync的,這包含了基本類型(如 u8)和包含他們的聚合類型。
爲了在線程間共享引用,Rust 提供了一個叫做Arc的 wrapper 類型。Arc實現了Send和Sync當且僅當T實現了Send和Sync。例如,一個Arc<RefCell<U>>
類型的對象不能在線程間傳送因爲RefCell並沒有實現Sync,因此Arc<RefCell<U>>
並不會實現Send。
這兩個特性允許你使用類型系統來確保你代碼在併發環境的特性。
線程
thread::spawn()方法接受一個閉包,它將會在一個新線程中執行。它返回一線程的句柄,這個句柄可以用來等待子線程結束並提取它的結果
use std::thread;
fn main() {
let handle = thread::spawn(|| {
"Hello from a thread!"
});
println!("{}", handle.join().unwrap());
}
安全共享可變狀態
# use std::sync::{Arc, Mutex};
# use std::thread;
# use std::time::Duration;
# fn main() {
# let data = Arc::new(Mutex::new(vec![1, 2, 3]));
# for i in 0..3 {
# let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[i] += 1;
});
# }
# thread::sleep(Duration::from_millis(50));
# }
首先,我們調用lock(),它獲取了互斥鎖。因爲這可能失敗,它返回一個Result,並且因爲這僅僅是一個例子,我們unwrap()結果來獲得一個數據的引用。現實中的代碼在這裏應該有更健壯的錯誤處理。下面我們可以隨意修改它,因爲我們持有鎖
通道
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
for i in 0..10 {
let tx = tx.clone();
thread::spawn(move || {
let answer = i * i;
tx.send(answer).unwrap();
});
}
for _ in 0..10 {
println!("{}", rx.recv().unwrap());
}
}
Panics
use std::thread;
let handle = thread::spawn(move || {
panic!("oops!");
});
let result = handle.join();
assert!(result.is_err());
我們的Thread返回一個Result,它允許我們檢查我們的線程是否發生panics。
錯誤處理
Option, Result
核心是用map組合來減少 case analysis
選擇你的保證
基礎指針類型
Box
Box\是一個“自我擁有的”,或者“裝箱”的指針。因爲它可以維持引用和包含的數據,它是數據的唯一的擁有者。特別的,當執行類似如下代碼時:
let x = Box::new(1);
let y = x;
// x no longer accessible here
這裏,裝箱被移動進了y。因爲x不再擁有它,此後編譯器不再允許程序猿使用x。
當一個裝箱(還沒有被移動的)離開了作用域,析構函數將會運行。這個析構函數負責釋放內部的數據。
&T和&mut T
這分別是不可變和可變引用。他們遵循“讀寫鎖”的模式,也就是你只可能擁有一個數據的可變引用,或者任意數量的不可變引用,但不是兩者都有。
這些指針不能在超出他們的生命週期的情況下被拷貝。
** const T和mut T**
這些是C風格的指針,並沒附加生命週期或所有權。他們只是指向一些內存位置,沒有其他的限制。他們能提供的唯一的保證是除非在標記爲unsafe的代碼中他們不會被解引用。
他們在構建像Vec這樣的安全,低開銷抽象時是有用的,不過應該避免在安全代碼中使用。
Rc
Rc\是一個引用計數指針。換句話說,這讓我們擁有相同數據的多個“有所有權”的指針,並且數據在所有指針離開作用域後將被釋放(析構函數將會執行)。
在內部,它包含一個共享的“引用計數”(也叫做“refcount”),每次Rc被拷貝時遞增,而每次Rc離開作用域時遞減。Rc的主要職責是確保共享的數據的析構函數被調用。
這裏內部的數據是不可變的,並且如果創建了一個循環引用,數據將會泄露。如果我們想要數據在存在循環引用時不被泄漏,我們需要一個垃圾回收器。
保證
這裏(Rc)提供的主要保證是,直到所有引用離開作用域後,相關數據纔會被銷燬。
當我們想要動態分配並在程序的不同部分共享一些(只讀)數據,且不確定哪部分程序會最後使用這個指針時,我們應該用Rc。當&T不可能靜態地檢查正確性,或者程序員不想浪費時間編寫反人類的代碼時,它可以作爲&T的可行的替代。
這個指針並不是線程安全的,並且Rust也不會允許它被傳遞或共享給別的線程。這允許你在不必要的情況下的原子性開銷。
開銷
多分配字段用來存放引用計數,拷貝時不是深拷貝,只增加引用計數,離開作用域時減引用數
Cell類型
Cell提供內部可變性。換句話說,他們包含的數據可以被修改,即便是這個類型並不能以可變形式獲取(例如,當他們位於一個&指針或Rc之後時)。
Cell
use std::cell::Cell;
let x = Cell::new(1);
let y = &x;
let z = &x;
x.set(2);
y.set(3);
z.set(4);
println!("{}", x.get());
let mut x = 1;
let y = &mut x;
let z = &mut x;
x = 2;
*y = 3;
*z = 4;
println!("{}", x);
上面兩端代碼要實現的功能是一樣的,但是隻有上面的可以編譯成功
開銷
使用Cell並沒有運行時開銷,不過你使用它來封裝一個很大的(Copy)結構體,可能更適合封裝單獨的字段爲Cell因爲每次寫入都會是一個結構體的完整拷貝。
RefCell
RefCell\也提供了內部可變性,不過並不限制爲Copy類型。
相對的,它有運行時開銷。RefCell在運行時使用了讀寫鎖模式,不像&T/&mut T那樣在編譯時執行。這通過borrow()和borrow_mut()函數來實現,它修改一個內部引用計數並分別返回可以不可變的和可變的解引用的智能指針。當智能指針離開作用域引用計數將被恢復。通過這個系統,我們可以動態的確保當有一個有效的可變借用時絕不會有任何其他有效的借用。如果程序猿嘗試創建一個這樣的借用,線程將會恐慌。
use std::cell::RefCell;
let x = RefCell::new(vec![1,2,3,4]);
{
println!("{:?}", *x.borrow())
}
{
let mut my_ref = x.borrow_mut();
my_ref.push(1);
}
與Cell相似,它主要用於難以或不可能滿足借用檢查的情況。大體上我們知道這樣的改變不會發生在一個嵌套的形式中,不過檢查一下是有好處的。
對於大型的,複雜的程序,把一些東西放入RefCell來將事情變簡單是有用的。例如,Rust編譯器內部的ctxt
結構體中的很多map都在這個封裝中。他們只會在創建時被修改一次(但並不是正好在初始化後),或者在明顯分開的地方多次多次修改。然而,因爲這個結構體被廣泛的用於各個地方,有效的組織可變和不可變的指針將會是困難的(也許是不可能的),並且可能產生大量的難以擴展的&指針。換句話說,RefCell提供了一個廉價(並不是零開銷)的方式來訪問它。之後,如果有人增加一些代碼來嘗試修改一個已經被借用的cell時,這將會產生(通常是決定性的)一個恐慌,並會被追溯到那個可惡的借用上。
相似的,在Servo的DOM中有很多可變量,大部分對於一個DOM類型都是本地的,不過有一些交錯在DOM中並修改了很多內容。使用RefCell和Cell來保護所有的變化可以讓我們免於擔心到處都是的可變性,並且同時也表明了何處正在發生變化。
注意如果是一個能用&指針的非常簡單的情形應該避免使用RefCell。
同步類型
注意非線程安全的類型不能在線程間傳遞,並且這是在編譯時檢查的。
Arc
Arc\就是一個使用原子引用計數版本的Rc(Atomic reference count,因此是“Arc”)。它可以在線程間自由的傳遞。
C++的shared_ptr與Arc類似,然而C++的情況中它的內部數據總是可以改變的。爲了語義上與C++的形式相似,我們應該使用Arc<Mutex>,Arc<RwLock>,或者Arc<UnsafeCell>1。最後一個應該只被用在我們能確定使用它並不會造成內存不安全性的情況下。記住寫入一個結構體不是一個原子操作,並且很多像vec.push()這樣的函數可以在內部重新分配內存併產生不安全的行爲,所以即便是單一環境也不足以證明UnsafeCell是安全的。
保證
類似Rc,它提供了當最後的Arc離開作用域時(不包含任何的循環引用)其內部數據的析構函數將被執行的(線程安全的)保證。
Mutex和RwLock
Mutex\和RwLock\通過RAII guard(guard是一類直到析構函數被調用時能保持一些狀態的對象)提供了互斥功能。對於這兩個類型,mutex直到我們調用lock()之前它都是無效的,此時直到我們獲取鎖這個線程都會被阻塞,同時它會返回一個guard。這個guard可以被用來訪問它的內部數據(可變的),而當guard離開作用域鎖將被釋放。
{
let guard = mutex.lock();
// guard dereferences mutably to the inner type
*guard += 1;
} // lock released when destructor runs
RwLock對多線程讀有額外的效率優勢。只要沒有writer,對於共享的數據總是可以安全的擁有多個reader;同時RwLock讓reader們獲取一個“讀取鎖”。這樣的鎖可以併發的獲取並通過引用計數記錄。writer必須獲取一個“寫入鎖”,它只有在所有reader都離開作用域時才能獲取。
保證
這兩個類型都提供了線程間安全的共享可變性,然而他們易於產生死鎖。一些額外的協議層次的安全性可以通過類型系統獲取。
開銷
他們在內部使用類原子類型來維持鎖,這樣的開銷非常大(他們可以阻塞處理器所有的內存讀取知道他們執行完畢)。而當有很多併發訪問時等待這些鎖也將是很慢的。
組合
外部函數接口
Borrow和AsRef
borrow
對於多數類型,當你想要獲取一個自我擁有或借用的類型,&T就足夠了。不過當有多於一種借用的值時,Borrow就能起作用了。引用和slice就是一個能體現這一點的地方:你可以有&[T]或者&mut [T]。如果我們想接受這兩種類型,Borrow就是你需要的:
use std::borrow::Borrow;
use std::fmt::Display;
fn foo<T: Borrow<i32> + Display>(a: T) {
println!("a is borrowed: {}", a);
}
let mut i = 5;
foo(&i);
foo(&mut i);
AsRef
AsRef特性是一個轉換特性。它用來在泛型中把一些值轉換爲引用。像這樣:
let s = "Hello".to_string();
fn foo<T: AsRef<str>>(s: T) {
let slice = s.as_ref();
}
選擇Borrow當你想要抽象不同類型的借用,或者當你創建一個數據結構它把自我擁有和借用的值看作等同的,例如哈希和比較。
選擇AsRef當你想要直接把一些值轉換爲引用,和當你在寫泛型代碼的時候。
發佈途徑
開發版,測試版,穩定版
不使用標準庫
Rust開發版
編譯器插件
內聯會變
固有功能
語言項
鏈接進階
基準測試
#![feature(test)]extern crate test;
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]mod tests {
use super::*;
use test::Bencher;
#[test]fn it_works() {
assert_eq!(4, add_two(2));
}
#[bench]fn bench_add_two(b: &mut Bencher) {
b.iter(|| add_two(2));
}
}
我們導入了testcrate,它包含了對基準測試的支持。我們也定義了一個新函數,帶有bench屬性。與一般的不帶參數的測試不同,基準測試有一個&mut Bencher參數。Bencher提供了一個iter方法,它接收一個閉包。這個閉包包含我們想要測試的代碼。
編寫基準測試的建議:
把初始代碼放於iter循環之外,只把你想要測試的部分放入它
確保每次循環都做了“同樣的事情”,不要累加或者改變狀態
確保外邊的函數也是冪等的(idempotent),基準測試runner可能會多次運行它
確保iter循環內簡短而快速,這樣基準測試會運行的很快同時校準器可以在合適的分辨率上調整運轉週期
確保iter循環執行簡單的工作,這樣可以幫助我們準確的定位性能優化(或不足)
裝箱語法和模式
目前唯一穩定的創建Box的方法是通過Box::new方法。並且不可能在一個模式匹配中穩定的析構一個Box。不穩定的box關鍵字可以用來創建和析構Box。
#![feature(box_syntax, box_patterns)]fn main() {
let b = Some(box 5);
match b {
Some(box n) if n < 0 => {
println!("Box contains negative number {}", n);
},
Some(box n) if n >= 0 => {
println!("Box contains non-negative number {}", n);
},
None => {
println!("No box");
},
_ => unreachable!()
}
}
注意這些功能目前隱藏在box_syntax(裝箱創建)和box_patterns(析構和模式匹配)gate 之後因爲它的語法在未來可能會改變。
返回指針
struct BigStruct {
one: i32,
two: i32,
// etc
one_hundred: i32,
}
fn foo(x: Box<BigStruct>) -> Box<BigStruct> {
Box::new(*x)
}
fn main() {
let x = Box::new(BigStruct {
one: 1,
two: 2,
one_hundred: 100,
});
let y = foo(x);
}
#![feature(box_syntax)]struct BigStruct {
one: i32,
two: i32,
// etc
one_hundred: i32,
}
fn foo(x: Box<BigStruct>) -> BigStruct {
*x
}
fn main() {
let x = Box::new(BigStruct {
one: 1,
two: 2,
one_hundred: 100,
});
let y: Box<BigStruct> = box foo(x);
}
這在不犧牲性能的前提下獲得了靈活性。
你可能會認爲這會給我們帶來很差的性能:返回一個值然後馬上把它裝箱?難道這在哪裏不都是最糟的嗎?Rust 顯得更聰明。這裏並沒有拷貝。main爲裝箱分配了足夠的空間,向foo傳遞一個指向他內存的x,然後foo直接向Box中寫入數據。
因爲這很重要所以要說兩遍:返回指針會阻止編譯器優化你的代碼。允許調用函數選擇它們需要如何使用你的輸出。
切片模式
如果你想在一個切片或數組上匹配,你可以通過slice_patterns功能使用&:
#![feature(slice_patterns)]fn main() {
let v = vec!["match_this", "1"];
match &v[..] {
["match_this", second] => println!("The second element is {}", second),
_ => {},
}
}
advanced_slice_patternsgate 讓你使用..表明在一個切片的模式匹配中任意數量的元素。這個通配符對一個給定的數組只能只用一次。如果在..之前有一個標識符,結果會被綁定到那個名字上。例如:
#![feature(advanced_slice_patterns, slice_patterns)]fn is_symmetric(list: &[u32]) -> bool {
match list {
[] | [_] => true,
[x, inside.., y] if x == y => is_symmetric(inside),
_ => false
}
}
fn main() {
let sym = &[0, 1, 4, 2, 4, 1, 0];
assert!(is_symmetric(sym));
let not_sym = &[0, 1, 7, 2, 4, 1, 0];
assert!(!is_symmetric(not_sym));
}
關聯常量
通過associated_consts功能,你像這樣可以定義常量:
#![feature(associated_consts)]trait Foo {
const ID: i32;
}
impl Foo for i32 {
const ID: i32 = 1;
}
fn main() {
assert_eq!(1, i32::ID);
}
也可以實現一個默認值:
#![feature(associated_consts)]trait Foo {
const ID: i32 = 1;
}
impl Foo for i32 {
}
impl Foo for i64 {
const ID: i32 = 5;
}
fn main() {
assert_eq!(1, i32::ID);
assert_eq!(5, i64::ID);
}
關聯常量並不一定要關聯在一個 trait 上。一個struct的impl塊或enum也行:
#![feature(associated_consts)]struct Foo;
impl Foo {
const FOO: u32 = 3;
}
自定義內存分配器
編譯器目前自帶兩個默認分配器:alloc_system和alloc_jemalloc(然而一些目標平臺並沒有 jemalloc)。這些分配器是正常的 Rust crate 幷包含分配和釋放內存的 routine 的實現。標準庫並不假設使用任何一個編譯,而且編譯器會在編譯時根據被產生的輸出類型決定使用哪個分配器。
編譯器產生的二進制文件默認會使用alloc_jemalloc(如果可用的話)。在這種情況下編譯器“控制了一切”,從它超過了最終鏈接的權利的角度來看。大體上這意味着分配器選擇可以被交給編譯器。
動態和靜態庫,然而,默認使用alloc_system。這裏 Rust 通常是其他程序的“客人”或者處於並沒有權決定應使用的分配器的世界。爲此它求助於標準 API(例如,malloc和free)來獲取和釋放內存。
#![feature(alloc_system)]extern crate alloc_system;
fn main() {
let a = Box::new(4); // allocates from the system allocatorprintln!("{}", a);
}
#![feature(alloc_jemalloc)]#![crate_type = "dylib"]extern crate alloc_jemalloc;
pub fn foo() {
let a = Box::new(4); // allocates from jemallocprintln!("{}", a);
}
# fn main() {}
# // only needed for rustdoc --test down below
# #![feature(lang_items)]// The compiler needs to be instructed that this crate is an allocator in order// to realize that when this is linked in another allocator like jemalloc should// not be linked in#![feature(allocator)]#![allocator]// Allocators are not allowed to depend on the standard library which in turn// requires an allocator in order to avoid circular dependencies. This crate,// however, can use all of libcore.#![no_std]// Let's give a unique name to our custom allocator#![crate_name = "my_allocator"]#![crate_type = "rlib"]// Our system allocator will use the in-tree libc crate for FFI bindings. Note// that currently the external (crates.io) libc cannot be used because it links// to the standard library (e.g. `#![no_std]` isn't stable yet), so that's why// this specifically requires the in-tree version.#![feature(libc)]extern crate libc;
// Listed below are the five allocation functions currently required by custom// allocators. Their signatures and symbol names are not currently typechecked// by the compiler, but this is a future extension and are required to match// what is found below.//// Note that the standard `malloc` and `realloc` functions do not provide a way// to communicate alignment so this implementation would need to be improved// with respect to alignment in that aspect.#[no_mangle]pub extern fn __rust_allocate(size: usize, _align: usize) -> *mut u8 {
unsafe { libc::malloc(size as libc::size_t) as *mut u8 }
}
#[no_mangle]pub extern fn __rust_deallocate(ptr: *mut u8, _old_size: usize, _align: usize) {
unsafe { libc::free(ptr as *mut libc::c_void) }
}
#[no_mangle]pub extern fn __rust_reallocate(ptr: *mut u8, _old_size: usize, size: usize,
_align: usize) -> *mut u8 {
unsafe {
libc::realloc(ptr as *mut libc::c_void, size as libc::size_t) as *mut u8
}
}
#[no_mangle]pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize,
_size: usize, _align: usize) -> usize {
old_size // this api is not supported by libc
}
#[no_mangle]pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize {
size
}
# // just needed to get rustdoc to test this
# fn main() {}
# #[lang = "panic_fmt"] fn panic_fmt() {}
# #[lang = "eh_personality"] fn eh_personality() {}
# #[lang = "eh_unwind_resume"] extern fn eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
extern crate my_allocator;
fn main() {
let a = Box::new(8); // allocates memory via our custom allocator crateprintln!("{}", a);
}