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程序設計語言-簡體中文版》
4.1 面向對象數據結構
4.1.1 元祖
元祖表示一個大小、類型固定的有序數據組。
let y = (2, "hello world");
let x: (i32, &str) = (3, "world hello");
// 然后呢,你能用很簡單的方式去訪問他們:
// 用 let 表達式
let (w, z) = y; // w=2, z="hello world"
// 用下標
let f = x.0; // f = 3
let e = x.1; // e = "world hello"
Rust雖然函數只有一個返回值,只需要通過元祖,我們可以很容易地返回多個返回值的組合。例子如下:
pub fn tuple_test1() {
let (number, persons) = demo_test();
println!("{}",number);
println!("{}",persons.name)
}
struct Person {
name: String,
age: u16,
}
fn demo_test() -> (u16, Person) {
let person = Person {
name: String::from("binwen"),
age: 12,
};
(14,person)
}
4.1.2 結構體
在Rust中,結構體是一個跟 tuple 類似 的概念。我們同樣可以將一些常用的數據、屬性聚合在一起,就形成了一個結構體。
所不同的是,Rust的結構體有三種最基本的形式。
1.具名結構體:通常接觸的基本都是這個類型的。
struct A {
attr1: i32,
atrr2: String,
}
內部每個成員都有自己的名字和類型。
2.元祖類型結構體:元組類型結構體
struct B(i32, u16, bool);
它可以看作是一個有名字的元組,具體使用方法和一般的元組基本類似。
3.空結構體
結構體內部也可以沒有任何成員。
struct D;
空結構體的內存占用為0。但是我們依然可以針對這樣的類型實現它的“成員函數”。
4.1.3 結構體的方法
Rust沒有繼承,它和Golang不約而同的選擇了trait(Golang叫Interface)作為其實現多態的基礎。
不同的是,golang是匿名繼承,rust是顯式繼承。如果需要實現匿名繼承的話,可以通過隱藏實現類型可以由generic配合trait作出。
struct Person {
name: String,
}
impl Person {
fn new(n: &str) -> Person {
Person {
name: n.to_string(),
}
}
fn greeting(&self) {
println!("{} say hello .", self.name);
}
}
fn main() {
let peter = Person::new("Peter");
peter.greeting();
}
上面的impl中,new 被 Person 這個結構體自身所調用,其特征是 :: 的調用,是一個類函數! 而帶有 self 的 greeting ,是一個成員函數。
4.1.4 再說結構體中引用的生命周期
本小節例子中,結構體的每個字段都是完整的屬于自己的。也就是說,每個字段的 owner 都是這個結構體。每個字段的生命周期最終都不會超過這個結構體。
但是如果想要持有一個(可變)引用的值怎么辦?例如
struct RefBoy {
loc: &i32,
}
則會得到一個編譯錯誤:
<anon>:6:14: 6:19 error: missing lifetime specifier [E0106]
<anon>:6 loc: & i32,
錯誤原因:
這種時候,你將持有一個值的引用,因為它本身的生命周期在這個結構體之外,所以對這個結構體而言,它無法準確的判斷獲知這個引用的生命周期,這在 Rust 編譯器而言是不被接受的。
這個時候就需要我們給這個結構體人為的寫上一個生命周期,并顯式地表明這個引用的生命周期。這個引用需要被借用檢查器進行檢查。寫法如下:
struct RefBoy<'a> {
loc: &'a i32,
}
這里解釋一下這個符號 <>,它表示的是一個 屬于 的關系,無論其中描述的是 生命周期 還是 泛型 。即: RefBoy in 'a。最終我們可以得出個結論,RefBoy 這個結構體,其生命周期一定不能比 'a 更長才行。
需要知道兩點:
1.結構體里的引用字段必須要有顯式的生命周期。
2.一個被顯式寫出生命周期的結構體,其自身的生命周期一定小于等于其顯式寫出的任意一個生命周期。
關于第二點,其實生命周期是可以寫多個的,用 , 分隔。
注:生命周期和泛型都寫在 <> 里,先生命周期后泛型,用,分隔。
一個比較有趣的例子,結構體的遞歸嵌套:
struct Manager {
name: String,
}
struct Teacher<'res> {
mana: &'res mut Manager,
}
struct Class<'res: 'row, 'row> {
teac: &'row mut Teacher<'res>,
}
fn main() {
let mut m = Manager {
name: String::from("jojo's"),
};
let mut t = Teacher { mana: &mut m };
let c = Class { teac: &mut t };
println!("{}", c.teac.mana.name);
c.teac.mana.name.push_str(" bizarre adverture");
println!("{}", c.teac.mana.name);
println!("Hello, world!");
}
4.2.方法
Rust中,通過impl可以對一個結構體添加成員方法。同時我們也看到了self這樣的關鍵字。
impl中的self,常見的有三種形式:self、 &self、&mut self 。
雖然方法和golang interface非常相像,但是還是要加上類似于java的self,主要原因在于Rust的所有權轉移機制。
Rust的笑話:“你調用了一下別人,然后你就不屬于你了”。
例如下面代碼:
struct A {
a: i32,
}
impl A {
pub fn show(self) {
println!("{}", self.a);
}
}
fn main() {
let ast = A{a: 12i32};
ast.show();
println!("{}", ast.a);
}
錯誤:
13:25 error: use of moved value: `ast.a` [E0382]
<anon>:13 println!("{}", ast.a);
因為 Rust 本身,在你調用一個函數的時候,如果傳入的不是一個引用,那么無疑,這個參數將被這個函數吃掉,即其 owner 將被 move 到這個函數的參數上。同理,impl 中的 self ,如果你寫的不是一個引用的話,也是會被默認的 move 掉。
4.2.1 &self 與 &mut self
關于 ref 和 mut ref 的寫法和被 move 的 self 寫法類似,只不過多了一個引用修飾符號。
需要注意的一點是,你不能在一個 &self 的方法里調用一個 &mut ref ,任何情況下都不行!
#[derive(Copy, Clone)]
struct A {
a: i32,
}
impl A {
pub fn show(&self) {
println!("{}", self.a);
// compile error: cannot borrow immutable borrowed content `*self` as mutable
// self.add_one();
}
pub fn add_two(&mut self) {
self.add_one();
self.add_one();
self.show();
}
pub fn add_one(&mut self) {
self.a += 1;
}
}
fn main() {
let mut ast = A{a: 12i32};
ast.show();
ast.add_two();
}
需要注意的是,一旦你的結構體持有一個可變引用,你,只能在 &mut self 的實現里去改變他!例子:
struct Person<'a>{
name :&'a mut Vec<i32>,
}
impl<'a> Person<'a>{
fn println_name(&mut self){
self.name.push(2090);
println!("{:?}",self.name);
}
fn println_name2(&self){
println!("{:?}",self.name);
}
// error[E0596]: cannot borrow `*self.name` as mutable, as it is behind a `&` reference
// --> src/trait_test.rs:19:9
// |
//18 | fn println_name3(&self){
// | ----- help: consider changing this to be a mutable reference: `&mut self`
//19 | self.name.push(2090);
// | ^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
// fn println_name3(&self){
// self.name.push(2090);
// println!("{:?}",self.name);
// }
}
pub fn trait_test1(){
println!("trait_test1");
let mut a = vec![1,2,3,4];
let mut person = Person{
name:&mut a,
};
person.name.push(120);
person.println_name();
person.println_name2();
let a = &person;
println!("{:?}",a.name);
// cannot borrow `*a.name` as mutable, as it is behind a `&` reference
// a.name.push(200);
//let a = &person;
//| ------- help: consider changing this to be a mutable reference: `&mut person`
//45 | println!("{:?}",a.name);
//46 | a.name.push(200);
//| ^^^^^^ `a` is a `&` reference, so the data it refers to cannot be borrowed as mutable
}
但是你可以在&self 的方法中讀取它。類似于如果一個結構體持有一個可變引用A,必須通過結構體的可變引用去改變A引用的值,而不能通過結構體的不可變引用去改變A引用的值,但是可以通過結構體的不可變引用去讀取A引用的值。
稍微復雜的例子:
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
上面了例子引出本章重要的一部分內容:trait。
4.3.trait
trait的簡單使用:使用trait定義一個特征(可以定義多個):
trait HasArea {
fn area(&self) -> f64;
}
trait里面的函數可以沒有(也可以有)函數體,實現代碼交給具體實現它的類型去補充:
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
fn main() {
let c = Circle {
x: 0.0f64,
y: 0.0f64,
radius: 1.0f64,
};
println!("circle c has an area of {}", c.area());
}
4.3.1 泛型參數約束
我們知道泛型可以指任意類型,但有時這不是我們想要的,需要給它一些約束。
use std::fmt::Debug;
fn foo<T: Debug>(s: T) {
println!("{:?}", s);
}
Debug是Rust內置的一個trait,為"{:?}"實現打印內容,函數foo接受一個泛型作為參數,并且約定其需要實現Debug。
可以使用多個trait對泛型進行約束:
use std::fmt::Debug;
fn foo<T: Debug + Clone>(s: T) {
s.clone();
println!("{:?}", s);
}
<T: Debug + Clone>中Debug和Clone使用+連接,標示泛型T需要同時實現這兩個trait。
4.3.1.1泛型參數約束簡化(通過where)
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
// where 從句
fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
// 或者
fn foo<T, K>(x: T, y: K)
where T: Clone,
K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
4.3.2 trait與內置類型
內置類型如:i32, i64等也可以添加trait實現,為其定制一些功能:
trait HasArea {
fn area(&self) -> f64;
}
impl HasArea for i32 {
fn area(&self) -> f64 {
*self as f64
}
}
5.area();
這樣的做法是有限制的。Rust 有一個“孤兒規則”:當你為某類型實現某 trait 的時候,必須要求類型或者 trait 至少有一個是在當前 crate 中定義的。你不能為第三方的類型實現第三方的 trait 。
在調用 trait 中定義的方法的時候,一定要記得讓這個 trait 可被訪問。
4.3.3 trait默認實現
trait Foo {
fn is_valid(&self) -> bool;
fn is_invalid(&self) -> bool { !self.is_valid() }
}
is_invalid是默認方法,Foo的實現者并不要求實現它,如果選擇實現它,會覆蓋掉它的默認行為。
4.3.4 trait的繼承
trait Foo {
fn foo(&self);
}
trait FooBar : Foo {
fn foobar(&self);
}
這樣FooBar的實現者也要同時實現Foo:
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
必須顯式實現Foo,這種寫法是錯誤的:
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
// --> src/trait_test_three.rs:18:5
// |
// 18 | fn foo(&self) { println!("foo"); }
// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a member of trait `FooBar`
// fn foo(&self) { println!("foo"); }
}
4.3.5 derive屬性
Rust提供了一個屬性derive來自動實現一些trait,這樣可以避免重復繁瑣地實現他們,能被derive使用的trait包括:Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd
#[derive(Debug)]
struct Foo;
fn main() {
println!("{:?}", Foo);
}
4.3.6 impl Trait
impl Trait 語法適用于短小的例子,它不過是一個較長形式的語法糖。這被稱為 trait bound.使用場景如下:
1.傳參數
// before
fn foo<T: Trait>(x: T) {
// after
fn foo(x: impl Trait) {
2.返回參數
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}
這個簽名表明,“我要返回某個實現了 Summary trait 的類型,但是不確定其具體的類型”。在例子中返回了一個 Tweet,不過調用方并不知情。
- 使用trait bound有條件地實現方法
通過使用帶有 trait bound 的泛型參數的 impl 塊,可以有條件地只為那些實現了特定 trait 的類型實現方法。例如,下面例子中的類型 Pair<T> 總是實現了 new 方法,不過只有那些為 T 類型實現了PartialOrd trait (來允許比較) 和 Display trait (來啟用打?。┑?Pair<T> 才會實現 cmp_display方法:
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self {
x,
y,
}
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
4.閉包
// before
fn foo() -> Box<Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
// after
fn foo() -> impl Fn(i32) -> i32 {
|x| x + 1
}
4.3.7 trait對象
此外,trait高級用法還有trait對象等等。這部分請查閱rustPrimer相應章節。
4.3.8 trait定義中的生命周期和可變性聲明
trait特性中的可變性和生命周期泛型定義必須和實現它的方法完全一致!不能缺??!
例子:
trait HelloArea{
fn areas<'a>(&mut self, a:&'a mut Vec<i32>, b:&'a mut Vec<i32>) -> &'a mut Vec<i32>;
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
impl HelloArea for Circle{
fn areas<'a>(&mut self, a:&'a mut Vec<i32>, b:&'a mut Vec<i32>) -> &'a mut Vec<i32>{
a.push(10000);
return a;
}
}
//compile_error!()
//error[E0053]: method `area` has an incompatible type for trait
// --> src/trait_test_two.rs:23:13
// |
//13 | fn area(&mut self) -> f64;
// | --------- type in trait
//...
//23 | fn area(&self) -> f64 {
// | ^^^^^ types differ in mutability