github地址:https://github.com/bradyjoestar/rustnotes(歡迎star!)
pdf下載鏈接:https://github.com/bradyjoestar/rustnotes/blob/master/Rust%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.pdf
參考:
https://rustcc.gitbooks.io/rustprimer/content/ 《RustPrimer》
https://kaisery.github.io/trpl-zh-cn/ 《Rust程序設計語言-簡體中文版》
2.1 前置知識
2.1.1 表達式和語句
其它語言中:
表達式是用來表達某含義的??梢园ǘx某值,或判斷某物,最終會有一個“值”的體現(xiàn),“Anything that has a value"。 比如說 var a=b就是表達式,是把b的值賦給a,或者 if (a == b)其中if()內(nèi)的也是表達式。 而表達式語句就是程序識別的一條執(zhí)行表達式的語句。 例如 var a=b; 這條是賦值語句,這里微小的差別就是加上了分號;作為語句結(jié)束符。 其實表達式簡單的可以理解成某語言的語法,而由這些語法構(gòu)成的一條執(zhí)行語句則是表達式語句。最直觀的根據(jù)是否有分號來判斷。
但是在rust語言中有點不同,
“l(fā)et a = 5”是表達式語句,即使沒有分號。
let y = (let a = 4);會返回下面的錯誤:
variable declaration using let is a statement。
2.1.2 rust doc
rust提供了從注釋到可瀏覽網(wǎng)頁文檔的生成方法,通過cargo doc的方式。
注釋主要有三種:行注釋,模塊注釋,文檔注釋。
Rust 也有特定的用于文檔的注釋類型,通常被稱為 文檔注釋(documentation comments),他們會生成 HTML 文檔。這些 HTML 展示公有 API 文檔注釋的內(nèi)容,他們意在讓對庫感興趣的程序員理解如何 使用 這個 crate,而不是它是如何被 實現(xiàn) 的。
文檔注釋使用三斜杠 /// 而不是兩斜桿并支持 Markdown 注解來格式化文本。文檔注釋就位于需要文檔的項的之前。
模塊注釋使用 //! ,行注釋使用 //
模塊注釋和文檔注釋用起來非常舒服的,遠比/* */舒服,IDEA對它支持很好,提供自動換行。
2.2 條件表達式
在rust中的條件表達式?jīng)]有switch語句。
2.2.1 if表達式
相對于 C 系語言,Rust 的 if 表達式的顯著特點是:
1.判斷條件不用小括號括起來。
2.它是表達式,而不是語句。
即使花括號里面有再多的表達式語句,它最后仍然需要返回一個值,是表達式。下面的寫法是正確的:
let x = 5;
let y = if x == 5 {
println!("hello test”)
10
} else {
15
}; // y: i32
需要說明的是if中條件判斷必須是bool類型,不能寫出if 5 這種判斷條件。
2.2.2 match語句
Rust 中沒有類似于 C 的 switch 關鍵字,但它有用于模式匹配的 match,能實現(xiàn)同樣的功能,并且強大太多。
match 的使用非常簡單,舉例如下:
let x = 5;
match x {
1 => {
println!("one")
},
2 => println!("two"),
3 => println!("three"),
4 => println!("four"),
5 => println!("five"),
_ => println!("something else"),
}
2.2.3 if let表達式
let y = 5;
if let y = 5 {
println!("{}", y); // 這里輸出為:5
}
if let實際上是一個 match 的簡化用法。設計這個特性的目的是,在條件判斷的時候,直接做一次模式匹配,方便代碼書寫,使代碼更緊湊。
2.3 循環(huán)表達式
2.3.1 for循環(huán)
Rust的for循環(huán)實際上和C語言的循環(huán)語句是不同的。這是為什么呢?因為,for循環(huán)不過是Rust編譯器提供的語法糖!在rust中,for 語句用于遍歷一個迭代器。
Rust 迭代器返回一系列的元素,每個元素是循環(huán)中的一次重復。然后它的值與 var 綁定,它在循環(huán)體中有效。每當循環(huán)體執(zhí)行完后,我們從迭代器中取出下一個值,然后我們再重復一遍。當?shù)髦胁辉儆兄禃r,for 循環(huán)結(jié)束。
for x in 0..10 {
println!("{}", x); // x: i32
}
沒有c中的這種用法:
// C 語言的 for 循環(huán)例子
for (x = 0; x < 10; x++) {
printf( "%d\n", x );
}
設計目的:
1.簡化邊界條件的確定,減少出錯;
2.減少運行時邊界檢查,提高性能。
當你需要記錄你已經(jīng)循環(huán)了多少次了的時候,你可以使用 .enumerate() 函數(shù)。比如:
for (i,j) in (5..10).enumerate() {
println!("i = {} and j = {}", i, j);
}
2.3.2 while循環(huán)
Rust 提供了 while 語句,條件表達式為真時,執(zhí)行語句體。當你不確定應該循環(huán)多少次時可選擇 while。
let mut x = 5; // mut x: i32
let mut done = false; // mut done: bool
while !done {
x += x - 3;
println!("{}", x);
if x % 5 == 0 {
done = true;
}
}
while條件判斷的類型必須是bool類型。
2.3.3 loop
loop專用于無限循環(huán),好處是對編譯和運行進行了優(yōu)化,跳過了表達式檢查。
2.3.4 break和continue
Rust 也提供了 break 和 continue 兩個關鍵字用來控制循環(huán)的流程。
1.break 用來跳出當前層的循環(huán);
2.continue 用來執(zhí)行當前層的下一次迭代
break和continue可以大大簡化代碼,不論在哪一門語言中。
for x in 0..10 {
if x % 2 == 0 { continue; }
println!("{}", x);
}
和
let mut x = 5;
loop {
x += x - 3;
println!("{}", x);
if x % 5 == 0 { break; }
}
2.3.5 label
你也許會遇到這樣的情形,當你有嵌套的循環(huán)而希望指定你的哪一個 break 或 continue 該起作用。就像大多數(shù)語言,默認 break 或 continue 將會作用于當前層的循環(huán)。當你想要一個 break 或 continue 作用于一個外層循環(huán),你可以使用標簽來指定你的 break 或 continue 語句作用的循環(huán)。
如下代碼只會在 x 和 y 都為奇數(shù)時打印他們:
'outer: for x in 0..10 {
'inner: for y in 0..10 {
if x % 2 == 0 { continue 'outer; } // continues the loop over x
if y % 2 == 0 { continue 'inner; } // continues the loop over y
println!("x: {}, y: {}", x, y);
}
}
2.4 Rust類型系統(tǒng)
2.4.1 可變性
rust 在聲明變量時,在變量前面加入 mut 關鍵字,變量就會成為可變綁定的變量。
通過可變綁定可以直接通過標識符修改內(nèi)存中的變量的值。
在綁定后仍然可以重新修改綁定類型。
例子:
fn main() {
let mut a: f64 = 1.0;
let b = 2.0f32;
//改變 a 的綁定
a = 2.0;
println!("{:?}", a);
//重新綁定為不可變
let a = a;
//不能賦值
//a = 3.0;
//類型不匹配
//assert_eq!(a, b);
}
2.4.2 原生類型
在所有rust的類型中,比較復雜的是字符串類型。當然不僅僅在rust中,包括golang等其它語言中,字符串類型和字符類型都是值得推敲的地方。
在本部分內(nèi)容中,單獨拿出字符類型和字符串類型放到最后進行討論。
字符類型和字符串類型最好和其它語言對比討論,例如go。
2.4.2.1 bool類型
Rust自帶了bool類型,其可能值為true或者false
let is_she_love_me = false;
let mut is_he_love_me: bool = true;
2.4.2.2數(shù)字類型
和其他類C系的語言不一樣,Rust用一種符號+位數(shù)的方式來表示其基本的數(shù)字類型。
可用的符號有 i、f、u
可用的位數(shù),都是2的n次冪,分別為8、16、32、64及size。
因為浮點類型最少只能用32位來表示,因此只能有f32和f64來表示。
2.4.2.3 自適應類型
isize和usize取決于你的操作系統(tǒng)的位數(shù)。簡單粗暴一點比如64位電腦上就是64位,32位電腦上就是32位。
但是需要注意的是,不能因為電腦是64位的,而強行將它等同于64,也就是說isize != i64,任何情況下你都需要強制轉(zhuǎn)換。
減少使用isize和usize,因為它會降低代碼可移植性。
2.4.2.4 數(shù)組 array
Rust的數(shù)組是被表示為[T;N]。其中N表示數(shù)組大小,并且這個大小一定是個編譯時就能獲得的整數(shù)值,T表示泛型類型,即任意類型。我們可以這么來聲明和使用一個數(shù)組:
let a = [8, 9, 10];
let b: [u8;3] = [8, 6, 5];
print!("{}", a[0]);
和Golang一樣,Rust的數(shù)組中的N(大?。┮彩穷愋偷囊徊糠郑碵u8; 3] != [u8; 4]。
Rust大小是固定的。
2.4.2.5 slice
Slice從直觀上講,是對一個Array的切片,通過Slice,你能獲取到一個Array的部分或者全部的訪問權(quán)限。和Array不同,Slice是可以動態(tài)的,但是呢,其范圍是不能超過Array的大小,這點和Golang是不一樣的。Golang slice可以超出Array的大小是存在一些問題的。
一個Slice的表達式可以為如下: &[T] 或者 &mut [T]。
這里&符號是一個難點,我們不妨放開這個符號,簡單的把它看成是Slice的規(guī)定。另外,同樣的,Slice也是可以通過下標的方式訪問其元素,下標也是從0開始。 可以這么聲明并使用一個Slice:
let arr = [1, 2, 3, 4, 5, 6];
let slice_complete = &arr[..]; // 獲取全部元素
let slice_middle = &arr[1..4]; // 獲取中間元素,最后取得的Slice為 [2, 3, 4] 。切片遵循左閉右開原則。
let slice_right = &arr[1..]; // 最后獲得的元素為[2, 3, 4, 5, 6],長度為5。
let slice_left = &arr[..3]; // 最后獲得的元素為[1, 2, 3],長度為3。
2.4.2.6 動態(tài)Vec
在Rust里,Vec被表示為 Vec<T>, 其中T是一個泛型。
let mut v1: Vec<i32> = vec![1, 2, 3]; // 通過vec!宏來聲明
let v2 = vec![0; 10]; // 聲明一個初始長度為10的值全為0的動態(tài)數(shù)組
println!("{}", v1[0]); // 通過下標來訪問數(shù)組元素
for i in &v1 {
print!("{}", i); // &Vec<i32> 可以通過 Deref 轉(zhuǎn)換成 &[i32]
}
println!("");
for i in &mut v1 {
*i = *i+1;
print!("{}", i); // 可變訪問
}
2.4.2.7 函數(shù)類型
相似于golang,在rust中函數(shù)也是一種類型,例子如下:
fn foo(x: i32) -> i32 { x+1 }
let x: fn(i32) -> i32 = foo;
assert_eq!(11, x(10));
2.4.2.8 枚舉類型
struct Student{
name: String,
}
enum Message{
School(String),
Location{x:i32,y:i32},
ChangeColor(i32, i32, i32),
Name(Student),
ExitColor,
}
fn main(){
let m = Message::ExitColor;
match m {
Message::ExitColor=>println!("{}","exited color"),
Message::ChangeColor(x,y,z) => println!("{}",x),
Message::Name(s) => println!("{}",s.name),
Message::School(s) => println!("{}",s),
Message::Location {x,y} => println!("{}",x),
}
}
與結(jié)構(gòu)體一樣,枚舉中的元素默認不能使用關系運算符進行比較 (如==, !=, >=), 也不支持像+和*這樣的雙目運算符,需要自己實現(xiàn),或者使用match進行匹配。
枚舉默認也是私有的,如果使用pub使其變?yōu)楣?,則它的元素也都是默認公有的。 這一點是與結(jié)構(gòu)體不同的:即使結(jié)構(gòu)體是公有的,它的域仍然是默認私有的。
rust枚舉與其他語言的枚舉不同的是在指定枚舉元素時定義它元素是由什么組成的。
一個result和match與enum的綜合例子:
fn main() {
println!("Hello, world!");
match returnResult() {
Ok(T) => println!("{}", T),
Err(RowError::error_1(T)) => println!("{}", T),
Err(RowError::error_2(x, y)) => println!("x={},y={}", x, y),
Err(RowError::error_3 { x, y }) => println!("x={},y={}", x, y),
}
}
enum RowError {
error_1(String),
error_2(i32, i32),
error_3 { x: i32, y: i32 },
}
fn returnResult() -> Result<String, RowError> {
let a = 90;
if a == 30 {
Ok(String::from("test"))
} else if a == 20 {
Err(RowError::error_1(String::from("error is coming")))
} else if a == 50 {
Err(RowError::error_2(5, 20))
} else {
Err(RowError::error_3 { x: 5, y: 30 })
}
}
2.4.2.9 字符串類型
如果要說rust的字符串類型,就不得不先提go的字符串類型。
2.4.2.9.1 Golang中的字符串類型
Go沒有專門的字符類型,存儲字符直接使用byte來存儲,字符串就是一串固定長度的字符連接起來字符序列。與其他編程語言不同之處在于,Go的字符串是字節(jié)組成,而其他的編程語言是字符組成。
Go的字符串可以把它當成字節(jié)數(shù)組來用,并且是不可變的。
Rust的字符串底層也是Vec<u8>動態(tài)數(shù)組,這點和Go的字符串有點類似,不同的是Go的字符串是定長的,無法修改的。
Rust和Go原聲在字符串里面支持unicode,這就導致了很大某方面的不同。
Go中字符串的例子:
package main
import (
"fmt"
"reflect"
)
func main() {
fmt.Println("test")
{
fmt.Println("example 1")
a := "testb"
fmt.Println(a[0]) //output 116
fmt.Println(len(a))
b := "testa你好"
fmt.Println(len(b))
c := b[0:8] // c is string type,alouth is slice of string
fmt.Println("c type is ",reflect.TypeOf(c))
fmt.Println(c)
if c == "testa你" {
fmt.Println("yes")
} else {
fmt.Println("no")
}
}
{
fmt.Println("example 2")
var str []byte
str = []byte("waht")
fmt.Println(string(str))
for i := 0; i < 10; i++ {
str = append(str, byte(i+100))
}
fmt.Println(string(str))
fmt.Println(str)
}
{
fmt.Println("example 3")
str1 := "??????";
fmt.Println(len(str1))
}
{
fmt.Println("example 4")
str2 := "hello world";
str2slice := str2[0:5]
str2slice = str2slice+" hello"
fmt.Println(str2slice)
fmt.Println(str2)
}
{
a := []int{1, 2, 3, 4}
fmt.Println(len(a))
b := a[0:1]
b = append(b, 5)
fmt.Println(len(b))
for _, value := range a {
fmt.Println(value)
}
str1 := "hello world"
fmt.Println(str1)
str1slice := str1[0:8]
fmt.Println(str1slice)
//compile error
//# command-line-arguments
//./main.go:78:21: cannot assign to ([]byte)(str1slice)
//[]byte(str1slice) = append([]byte(str1slice),byte('a'))
//fmt.Println(str1slice)
}
}
輸出結(jié)果:
example 1
116
5
11
c type is string
testa你
yes
example 2
waht
wahtdefghijklm
[119 97 104 116 100 101 102 103 104 105 106 107 108 109]
example 3
18
example 4
hello hello
hello world
從上面的例子中,可以看出存儲unicode字符最大的特點是不同的字符串所占的字節(jié)數(shù)不同。例如梵文占6個字節(jié),漢字占3個字節(jié)。
另外golang中的string slice不能進行append操作,可以把它當成一個固定的string類型來使用,例如可以比較二者包含的字符串是否相等。
另外String類型底層是byte數(shù)組,但是底層的byte數(shù)組沒有暴露出來,所以無法修改長度。
2.4.2.9.2 Rust中的字符串類型
常用rust字符串類型為&str和String,前者是字符串的引用,后者是基于堆創(chuàng)建的,可增長的字符串。
2.4.2.9.2.1 &str
let s ="hello world";那s的類型就是&str,右邊稱為字符串字面量literal,程序編譯成二進制文件后,這個字符串會被保存在文件內(nèi)部,所以s是特定位置字符串的引用,這就是為什么s是&str類型。
str生命周期是static,但是引用是有生命周期限制的。
可以在字符串字面量前加上r來避免轉(zhuǎn)義
//沒有轉(zhuǎn)義序列
let d: &'static str = r"abc \n abc";
//等價于
let c: &'static str = "abc \\n abc";
2.4.2.9.2.2 String
這時候,一種在堆上聲明的字符串String被設計了出來。 它能動態(tài)的去增長或者縮減,那么怎么聲明它呢?
let x:&'static str = "hello";
let mut y:String = x.to_string();
println!("{}", y);
y.push_str(", world");
println!("{}", y);
那么如何將一個String重新變成&str呢?用 &* 符號
fn use_str(s: &str) {
println!("I am: {}", s);
}
fn main() {
let s = "Hello".to_string();
use_str(&*s);
}
&是兩個符號&和的組合,按照Rust的運算順序,先對String進行Deref,也就是操作。
由于String實現(xiàn)了 impl Deref<Target=str> for String,這相當于一個運算符重載,所以你就能通過獲得一個str類型。但是我們知道,單獨的str是不能在Rust里直接存在的,因此,我們需要先給他進行&操作取得&str這個結(jié)果。
涵蓋大部分&str和String的例子。
fn main() {
println!("Hello, world!");
{
println!("example 1");
let mut str1 = String::from("你好 hello");
str1.push_str(" str1");
let mut str2 = &mut str1;
str2.push_str(" test ");
str2.push('a');
println!("{:?}", str1);
let mut str3 = &mut str1;
str3.push_str(" test3 ");
str3.push('3');
println!("{:?}", str1);
// compile error
// error[E0502]: cannot borrow `str1` as immutable because it is also borrowed as mutable
// --> src/main.rs:14:25
// |
//11 | let mut str3 = &mut str1;
// | --------- mutable borrow occurs here
//...
//14 | println!("{:?}",str1);
// | ^^^^ immutable borrow occurs here
//...
//17 | println!("{:?}",str3);
// | ---- mutable borrow later used here
// println!("{:?}",str3);
}
{
println!("example 2");
let mut v1 = vec![1, 2, 3, 4];
let mut v2 = &mut v1;
v2.push(100);
println!("{:?}", v2);
let mut v3 = &mut v1;
v3.push(20);
//compile error
println!("{:?}", v1);
//error[E0502]: cannot borrow `v1` as immutable because it is also borrowed as mutable
// --> src/main.rs:41:25
// |
//37 | let mut v3 = &mut v1;
// | ------- mutable borrow occurs here
//...
//41 | println!("{:?}",v1);
// | ^^ immutable borrow occurs here
//42 | println!("{:?}",v3);
// | -- mutable borrow later used here
// println!("{:?}",v3);
}
{
println!("example 3");
let mut str1 = String::from("hello");
//error[E0277]: the type `std::string::String` cannot be indexed by `{integer}`
// --> src/main.rs:59:23
// |
//59 | let answer = &str1[0];
// | ^^^^^^^ `std::string::String` cannot be indexed by `{integer}`
// |
// = help: the trait `std::ops::Index<{integer}>` is not implemented for `std::string::String`
// compile error
//let answer = &str1[0];
}
{
println!("example 4");
//how to loop the string
for c in "??????".chars() {
println!("{}", c);
}
}
{
println!("example 5");
let mut a = String::from("testa你好");
let mut b = &a[0..4];
println!("{}", b);
//This will panic
//thread 'main' panicked at 'byte index 6 is not a char boundary; it is inside '你' (bytes 5..8) of `testa你好`', src/libcore/str/mod.rs:2027:5
//let mut c = &a[0..6];
//println!("{}",c);
}
{
println!("example 6");
let mut a = String::from("testa你好");
println!("the length of a is {}", a.len());
}
{
println!("example 7");
let mut a = String::from("test");
let mut b = &mut a;
b.push_str(" str");
println!("{}", b);
println!("{}", a);
//compile error
//error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
// --> src/main.rs:102:23
// |
//99 | let mut b = &mut a;
// | ------ mutable borrow occurs here
//...
//102 | println!("{}",a);
// | ^ immutable borrow occurs here
//...
//106 | println!("{}",b);
// | - mutable borrow later used here
//
//error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
// --> src/main.rs:105:23
// |
//99 | let mut b = &mut a;
// | ------ mutable borrow occurs here
//...
//105 | println!("{}",a);
// | ^ immutable borrow occurs here
//106 | println!("{}",b);
// | - mutable borrow later used here
// println!("{}",a);
// println!("{}",b);
}
{
println!("example 8");
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移動了,不能繼續(xù)使用
// write bug
// error[E0369]: binary operation `+` cannot be applied to type `&std::string::String`
//let s3 = &s1 + &s2; // 注意 s1 被移動了,不能繼續(xù)使用
println!("{}", s3);
// error[E0382]: borrow of moved value: `s1`
// --> src/main.rs:141:23
// |
//135 | let s1 = String::from("Hello, ");
// | -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
//136 | let s2 = String::from("world!");
//137 | let s3 = s1 + &s2; // 注意 s1 被移動了,不能繼續(xù)使用
// | -- value moved here
//...
//141 | println!("{}",s1);
// | ^^ value borrowed here after move
// compile error
println!("{}", s2);
}
}
輸出結(jié)果:
example 1
"你好 hello str1 test a"
"你好 hello str1 test a test3 3"
example 2
[1, 2, 3, 4, 100]
[1, 2, 3, 4, 100, 20]
example 3
example 4
example 5
test
example 6
the length of a is 11
example 7
test str
test str
example 8
Hello, world!
world!
需要注意的主要就是:String類型底層實現(xiàn)是vec<u8>,unicode類型,并且拿著引用可以改變String內(nèi)容。有點類似中在go做一個特殊的String類型,并且內(nèi)部包著一個byte數(shù)組。