咸宁公司做网站医院网站云服务器配置
文章目录
- 6 方法 Method
 - 6.1 定义方法
 - self、&self 和 &mut self
 
- 6.2 自动引用和解引用
 - 6.3 关联函数
 
- 7 泛型和特征
 - 7.1 泛型 Generics
 - 7.1.1 结构体中使用泛型
 - 7.1.2 枚举中使用泛型
 - 7.1.3 方法中使用泛型
 - 为具体的泛型类型实现方法
 
- 7.1.4 const 泛型
 
- 7.2 特征 Trait
 - 7.2.1 为类型实现特征
 - 7.2.2 使用特征作为函数参数
 - 7.2.3 特征约束(trait bound)
 - 多重约束
 - where约束
 
- 7.2.4 函数返回中的impl trait
 - 7.2.5 两个综合例子
 - 为自定义类型实现 `+` 操作
 - 自定义类型的打印输出
 
- 7.3 特征对象
 
6 方法 Method
Rust中,方法往往和对象成对出现:object.method()。Rust 的方法往往跟结构体、枚举、特征(Trait)一起使用
6.1 定义方法
Rust 使用 impl 来定义方法,例如以下代码:
struct Circle {x: f64,y: f64,radius: f64,
}impl Circle {// new是Circle的关联函数,因为它的第一个参数不是self,且new并不是关键字// 这种方法往往用于初始化当前结构体的实例fn new(x: f64, y: f64, radius: f64) -> Circle {Circle {x: x,y: y,radius: radius,}}// Circle的方法,&self表示借用当前的Circle结构体fn area(&self) -> f64 {std::f64::consts::PI * (self.radius * self.radius)}
}
 
注:Rust 的对象定义和方法定义是分离的,这种数据和使用分离的方式,会给予使用者极高的灵活度。
self、&self 和 &mut self
在一个 impl 块内,Self 指代被实现方法的结构体类型,self 指代此类型的实例,即为哪个结构体实现方法,那么 self 就是指代哪个结构体的实例。self 依然有所有权的概念:
self表示Rectangle的所有权转移到该方法中,这种形式用的较少&self表示该方法对Rectangle的不可变借用&mut self表示可变借用
self 的使用就跟函数参数一样,要严格遵守 Rust 的所有权规则。使用方法代替函数有以下好处:
- 不用在函数签名中重复书写 
self对应的类型 - 代码的组织性和内聚性更强,对于代码维护和阅读来说,好处巨大
 
在 Rust 中,允许方法名跟结构体的字段名相同,往往适用于实现 getter 访问器:
pub struct Rectangle {width: u32,height: u32,
}impl Rectangle {pub fn new(width: u32, height: u32) -> Self {Rectangle { width, height }}pub fn width(&self) -> u32 {return self.width;}
}fn main() {let rect1 = Rectangle::new(30, 50);println!("{}", rect1.width());
}
 
用这种方式,我们可以把 Rectangle 的字段设置为私有属性,只需把它的 new 和 width 方法设置为公开可见,那么用户就可以创建一个矩形,同时通过访问器 rect1.width() 方法来获取矩形的宽度,因为 width 字段是私有的,当用户访问 rect1.width 字段时,就会报错。注意在此例中,Self 指代的就是被实现方法的结构体 Rectangle。
6.2 自动引用和解引用
在 C/C++ 语言中,有两个不同的运算符来调用方法:. 直接在对象上调用方法,而 -> 在一个对象的指针上调用方法,这时需要先解引用指针。换句话说,如果 object 是一个指针,那么 object->something() 和 (*object).something() 是一样的。
Rust 并没有一个与 -> 等效的运算符;相反,Rust 有一个叫 自动引用和解引用的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。他是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &、&mut 或 * 以便使 object 与方法签名匹配。也就是说,这些代码是等价的:
p1.distance(&p2);
(&p1).distance(&p2);
 
第一行看起来简洁的多。这种自动引用的行为之所以有效,是因为方法有一个明确的接收者———— self 的类型。在给出接收者和方法名的前提下,Rust 可以明确地计算出方法是仅仅读取(&self),做出修改(&mut self)或者是获取所有权(self)。事实上,Rust 对方法接收者的隐式借用让所有权在实践中更友好。
6.3 关联函数
思考一个问题,如何为一个结构体定义一个构造器方法?也就是接受几个参数,然后构造并返回该结构体的实例。其实答案在开头的代码片段中就给出了,很简单,参数中不包含 self 即可。
这种定义在 impl 中且没有 self 的函数被称之为关联函数: 因为它没有 self,不能用 f.read() 的形式调用,因此它是一个函数而不是方法,它又在 impl 中,与结构体紧密关联,因此称为关联函数。
在之前的代码中,我们已经多次使用过关联函数,例如 String::from,用于创建一个动态字符串。
impl Rectangle {fn new(w: u32, h: u32) -> Rectangle {Rectangle { width: w, height: h }}
}
 
Rust 中有一个约定俗成的规则,使用
new来作为构造器的名称,出于设计上的考虑,Rust 特地没有用new作为关键字
因为是函数,所以不能用 . 的方式来调用,我们需要用 :: 来调用,例如 let sq = Rectangle::new(3, 3);。这个方法位于结构体的命名空间中::: 语法用于关联函数和模块创建的命名空间。
Rust 允许我们为一个结构体定义多个 impl 块,目的是提供更多的灵活性和代码组织性,例如当方法多了后,可以把相关的方法组织在同一个 impl 块中,那么就可以形成多个 impl 块,各自完成一块儿目标。
除了结构体,还可以为枚举、特征(trait)实现方法,如:
#![allow(unused)]
enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}impl Message {fn call(&self) {// 在这里定义方法体}
}fn main() {let m = Message::Write(String::from("hello"));m.call();
}
 
7 泛型和特征
7.1 泛型 Generics
使用泛型参数,有一个先决条件,必需在使用前对其进行声明:
fn largest<T>(list: &[T]) -> T {}
 
可以这样理解这个函数定义:函数 largest 有泛型类型 T,它有个参数 list,其类型是元素为 T 的数组切片,最后,该函数返回值的类型也是 T。
7.1.1 结构体中使用泛型
结构体中的字段类型也可以用泛型来定义,下面代码定义了一个坐标点 Point,它可以存放任何类型的坐标值:
struct Point<T> {x: T,y: T,
}fn main() {let integer = Point { x: 5, y: 10 };let float = Point { x: 1.0, y: 4.0 };
}
 
这里有两点需要特别的注意:
- 提前声明,跟泛型函数定义类似,首先我们在使用泛型参数之前必需要进行声明 
Point<T>,接着就可以在结构体的字段类型中使用T来替代具体的类型 - x 和 y 是相同的类型,若需要不同类型,将声明改为
struct Point<T,U> { x: T, y: U, } 
7.1.2 枚举中使用泛型
Option 枚举类型
enum Option<T> {Some(T),None,
}
 
Option<T> 是一个拥有泛型 T 的枚举类型,它第一个成员是 Some(T),存放了一个类型为 T 的值。可以在任何一个需要返回值的函数中,去使用 Option<T> 枚举类型来做为返回值,用于返回一个任意类型的值 Some(T),或者没有值 None。
Result枚举类型
enum Result<T, E> {Ok(T),Err(E),
}
 
主要用于函数返回值,如果函数正常运行,则最后返回一个 Ok(T),T 是函数具体的返回值类型,如果函数异常运行,则返回一个 Err(E),E 是错误类型。例如打开一个文件:如果成功打开文件,则返回 Ok(std::fs::File),因此 T 对应的是 std::fs::File 类型;而当打开文件时出现问题时,返回 Err(std::io::Error),E 对应的就是 std::io::Error 类型。
7.1.3 方法中使用泛型
在结构体的方法中定义额外的泛型参数,就跟泛型函数一样:
struct Point<T, U> {x: T,y: U,
}impl<T, U> Point<T, U> {fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {Point {x: self.x,y: other.y,}}
}fn main() {let p1 = Point { x: 5, y: 10.4 };let p2 = Point { x: "Hello", y: 'c'};let p3 = p1.mixup(p2);println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
 
这个例子中,T,U 是定义在结构体 Point 上的泛型参数,V,W 是单独定义在方法 mixup 上的泛型参数,可以理解为,一个是结构体泛型,一个是函数泛型。
为具体的泛型类型实现方法
对于 Point<T> 类型,你不仅能定义基于 T 的方法,还能针对特定的具体类型,进行方法定义:
impl Point<f32> {fn distance_from_origin(&self) -> f32 {(self.x.powi(2) + self.y.powi(2)).sqrt()}
}
 
这段代码意味着 Point<f32> 类型会有一个方法 distance_from_origin,而其他 T 不是 f32 类型的 Point<T> 实例则没有定义此方法。这个方法计算点实例与坐标(0.0, 0.0) 之间的距离,并使用了只能用于浮点型的数学运算符。
这样我们就能针对特定的泛型类型实现某个特定的方法,对于其它泛型类型则没有定义该方法
7.1.4 const 泛型
const 泛型,针对值的泛型,可以用于处理数组长度的问题:
fn display_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {println!("{:?}", arr);
}
fn main() {let arr: [i32; 3] = [1, 2, 3];display_array(arr);let arr: [i32; 2] = [1, 2];display_array(arr);
}
 
如上所示,我们定义了一个类型为 [T; N] 的数组,其中 T 是一个基于类型的泛型参数,重点在于 N 这个泛型参数,它是一个基于值的泛型参数!因为它用来替代的是数组的长度。
注意的是需要对
T加一个限制std::fmt::Debug,该限制表明T可以用在println!("{:?}", arr)中,因为{:?}形式的格式化输出需要arr实现该特征。
N 就是 const 泛型,定义的语法是 const N: usize,表示 const 泛型 N ,它基于的值类型是 usize。
Rust 通过在编译时进行泛型代码的 单态化(monomorphization)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。
编译器所做的工作正好与我们创建泛型函数的步骤相反,编译器寻找所有泛型代码被调用的位置并针对具体类型生成代码。
7.2 特征 Trait
特征类似于其他语言中的接口,定义了一组可以被共享的行为,只要实现了特征,你就能使用这组行为。
7.2.1 为类型实现特征
因为特征只定义行为看起来是什么样的,因此我们需要为类型实现具体的特征,定义行为具体是怎么样的。
首先来为 Post 和 Weibo 实现 Summary 特征:
//特征定义,也可以在特征中定义具有默认实现的方法
pub trait Summary {fn summarize(&self) -> String;
}pub struct Post {pub title: String, // 标题pub author: String, // 作者pub content: String, // 内容
}
//特征实现
impl Summary for Post {fn summarize(&self) -> String {format!("文章{}, 作者是{}", self.title, self.author)}
}pub struct Weibo {pub username: String,pub content: String
}impl Summary for Weibo {fn summarize(&self) -> String {format!("{}发表了微博{}", self.username, self.content)}
}
 
实现特征的语法与为结构体、枚举实现方法很像:impl Summary for Post,读作“为 Post 类型实现 Summary 特征”,然后在 impl 的花括号中实现该特征的具体方法。
关于特征实现与定义的位置,有一条非常重要的原则:如果你想要为类型
A实现特征T,那么A或者T至少有一个是在当前作用域中定义的!
7.2.2 使用特征作为函数参数
定义一个函数,使用特征作为函数参数:
pub fn notify(item: &impl Summary) {println!("Breaking news! {}", item.summarize());
}
 
impl Summary,它的意思是 实现了Summary特征 的 item 参数。
你可以使用任何实现了 Summary 特征的类型作为该函数的参数,同时在函数体内,还可以调用该特征的方法,例如 summarize 方法。具体的说,可以传递 Post 或 Weibo 的实例来作为参数,而其它类如 String 或者 i32 的类型则不能用做该函数的参数,因为它们没有实现 Summary 特征。
7.2.3 特征约束(trait bound)
虽然 impl Trait 这种语法非常好理解,但是实际上它只是一个语法糖:
pub fn notify<T: Summary>(item: &T) {println!("Breaking news! {}", item.summarize());
}
 
真正的完整书写形式如上所述,形如 T: Summary 被称为特征约束。
在简单的场景下 impl Trait 这种语法糖就足够使用,但是对于复杂的场景,特征约束可以让我们拥有更大的灵活性和语法表现能力,例如一个函数接受两个 impl Summary 的参数:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {}
 
如果函数两个参数是不同的类型,那么上面的方法很好,只要这两个类型都实现了 Summary 特征即可。但是如果我们想要强制函数的两个参数是同一类型呢?上面的语法就无法做到这种限制,此时我们只能使特征约束来实现:
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
 
泛型类型 T 说明了 item1 和 item2 必须拥有同样的类型,同时 T: Summary 说明了 T 必须实现 Summary 特征。
多重约束
pub fn notify<T: Summary + Display>(item: &T) {}
 
通过这两个特征,就可以使用 item.summarize 方法,以及通过 println!("{}", item) 来格式化输出 item。
where约束
当特征约束变得很多时,函数的签名将变得很复杂:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {}
 
严格来说,上面的例子还是不够复杂,但是我们还是能对其做一些形式上的改进,通过 where:
fn some_function<T, U>(t: &T, u: &U) -> i32where T: Display + Clone,U: Clone + Debug
{}
 
7.2.4 函数返回中的impl trait
可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征:
fn returns_summarizable() -> impl Summary {Weibo {username: String::from("sunface"),content: String::from("m1 max太厉害了,电脑再也不会卡",)}
}
 
因为 Weibo 实现了 Summary,因此这里可以用它来作为返回值。要注意的是,虽然我们知道这里是一个 Weibo 类型,但是对于 returns_summarizable 的调用者而言,他只知道返回了一个实现了 Summary 特征的对象,但是并不知道返回了一个 Weibo 类型。
当函数返回的真实类型非常复杂,不知道该怎么声明时(毕竟 Rust 要求你必须标出所有的类型),此时就可以用 impl Trait 的方式简单返回。例如,闭包和迭代器就是很复杂,只有编译器才知道那玩意的真实类型,如果让你写出来它们的具体类型,估计内心有一万只草泥马奔腾,好在你可以用 impl Iterator 来告诉调用者,返回了一个迭代器,因为所有迭代器都会实现 Iterator 特征。
7.2.5 两个综合例子
为自定义类型实现 + 操作
 
在 Rust 中除了数值类型的加法,String 也可以做加法,因为 Rust 为该类型实现了 std::ops::Add 特征,同理,如果我们为自定义类型实现了该特征,那就可以自己实现 Point1 + Point2 的操作:
use std::ops::Add;// 为Point结构体派生Debug特征,用于格式化输出
#[derive(Debug)]
struct Point<T: Add<T, Output = T>> { //限制类型T必须实现了Add特征,否则无法进行+操作。x: T,y: T,
}impl<T: Add<T, Output = T>> Add for Point<T> {type Output = Point<T>;fn add(self, p: Point<T>) -> Point<T> {Point{x: self.x + p.x,y: self.y + p.y,}}
}fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {a + b
}fn main() {let p1 = Point{x: 1.1f32, y: 1.1f32};let p2 = Point{x: 2.1f32, y: 2.1f32};println!("{:?}", add(p1, p2));let p3 = Point{x: 1i32, y: 1i32};let p4 = Point{x: 2i32, y: 2i32};println!("{:?}", add(p3, p4));
}
 
自定义类型的打印输出
在开发过程中,往往只要使用 #[derive(Debug)] 对我们的自定义类型进行标注,即可实现打印输出的功能:
#[derive(Debug)]
struct Point{x: i32,y: i32
}
fn main() {let p = Point{x:3,y:3};println!("{:?}",p);
}
 
但是在实际项目中,往往需要对我们的自定义类型进行自定义的格式化输出,以让用户更好的阅读理解我们的类型,此时就要为自定义类型实现 std::fmt::Display 特征:
#![allow(dead_code)]use std::fmt;
use std::fmt::{Display};#[derive(Debug,PartialEq)]
enum FileState {Open,Closed,
}#[derive(Debug)]
struct File {name: String,data: Vec<u8>,state: FileState,
}impl Display for FileState {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {match *self {FileState::Open => write!(f, "OPEN"),FileState::Closed => write!(f, "CLOSED"),}}
}impl Display for File {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "<{} ({})>",self.name, self.state)}
}impl File {fn new(name: &str) -> File {File {name: String::from(name),data: Vec::new(),state: FileState::Closed,}}
}fn main() {let f6 = File::new("f6.txt");//...println!("{:?}", f6);println!("{}", f6);
}
 
7.3 特征对象
可以通过 & 引用或者 Box<T> 智能指针的方式来创建特征对象。
trait Draw {fn draw(&self) -> String;
}impl Draw for u8 {fn draw(&self) -> String {format!("u8: {}", *self)}
}impl Draw for f64 {fn draw(&self) -> String {format!("f64: {}", *self)}
}// 若 T 实现了 Draw 特征, 则调用该函数时传入的 Box<T> 可以被隐式转换成函数参数签名中的 Box<dyn Draw>
fn draw1(x: Box<dyn Draw>) {// 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法x.draw();
}fn draw2(x: &dyn Draw) {x.draw();
}fn main() {let x = 1.1f64;// do_something(&x);let y = 8u8;// x 和 y 的类型 T 都实现了 `Draw` 特征,因为 Box<T> 可以在函数调用时隐式地被转换为特征对象 Box<dyn Draw> // 基于 x 的值创建一个 Box<f64> 类型的智能指针,指针指向的数据被放置在了堆上draw1(Box::new(x));// 基于 y 的值创建一个 Box<u8> 类型的智能指针draw1(Box::new(y));draw2(&x);draw2(&y);
}
 
上面代码,有几个非常重要的点:
draw1函数的参数是Box<dyn Draw>形式的特征对象,该特征对象是通过Box::new(x)的方式创建的draw2函数的参数是&dyn Draw形式的特征对象,该特征对象是通过&x的方式创建的dyn关键字只用在特征对象的类型声明上,在创建时无需使用dyn
因此,可以使用特征对象来代表泛型或具体的类型。
泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是静态分发(static dispatch),因为是在编译期完成的,对于运行期性能完全没有任何影响。
与静态分发相对应的是动态分发(dynamic dispatch),在这种情况下,直到运行时,才能确定需要调用什么方法。之前代码中的关键字 dyn 正是在强调这一“动态”的特点。当使用特征对象时,Rust 必须使用动态分发。
特征对象特点:
- 特征对象大小不固定(对于不同的类型可以实现相同特征)
 - 几乎总是使用特征对象的引用方式,如 
&dyn Draw、Box<dyn Draw>(引用大小是固定的,占用两个指针大小:ptr-指向实现了特征的具体类型的实例/vptr-指向虚表vtable) 
在Rust中,一般
Self代表类型,self指代当前实例对象。
不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的:
- 方法的返回类型不能是 
Self - 方法没有任何泛型参数
 
