Rust学习笔记4 面向对象编程

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,不过调用方并不知情。

  1. 使用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342