Rust方法
在大多数面向对象的语言中都存在方法,方法一般和类关联在一起。在Rust中也是类似的,方法和对象总是一起出现。Rust的方法和结构体,枚举,特征一起使用。
定义方法
Rust使用关键字impl
来定义方法,例如:
#![allow(unused)]
fn main() {
// 这个圆的结构体定义可以看做是由x,y来定位圆的圆心,radius是圆的半径。
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
// 这种方法往往用于初始化当前结构体的实例
fn new(x: f64, y: f64, radius: f64) -> Circle {
Circle {
x,
y,
radius,
}
}
// Circle的方法,&self表示借用当前的Circle结构体
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
}
因此,在rust中定义方法的格式如下:
代码语言:javascript复制impl 结构体名|枚举名|特征名{
函数1
函数2
...
}
另外一点需要注意的是,&self实际上是self:&Self的简写,在一个impl块内,Self指代被实现方法的结构体(枚举或者特征)类型,self指代此类型的实例。因此在上面这个例子中,&Self实际指代Circle这个结构体类型,而self则是该结构体的一个实例化对象。实际上和其它面向对象语言是差不多的。(self和C 的this指针实际上是类似的,只不过this是C 方法的隐含参数,而rust的self和python的self在表现形式上几乎是一模一样的。)方法的第一个参数必须有一个名为 self 的Self 类型的参数,所以 Rust 让你在第一个参数位置上只用 self 这个名字来缩写。
还有一点需要注意,那就是new是Circle的关联函数,因为它的第一个参数不是self,且new并不是关键字;而area是Circle的方法,它的第一个参数是&self。
需要注意的是,self 依然有所有权的概念:
- self 表示 Circle 的所有权转移到该方法中,这种形式用的较少
- &self 表示该方法对 Circle 的不可变借用
- &mut self 表示可变借用
方法名和结构体字段名相同
在 Rust 中,允许方法名跟结构体的字段名相同,一般来说,方法跟字段同名,往往适用于实现get访问器,例如:
代码语言:javascript复制pub struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
pub fn new(width: u32, height: u32) -> Self { // Self指代的类型是Rectangle
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 字段时,就会报错。
上面这段代码同时展示了关联函数new在使用的时候是结构体名::函数名
,而方法则是obj.method
。接着在main函数中加上下面两行代码。
let rect2 = &Rectangle::new(3, 3);
println!("{}", rect2.width());
可以看到,对于一个引用,我们依旧是采用点(.)运算符来调用width方法的,没有C/C 中的区分,指针使用->,对象使用点(.)。这背后是Rust拥有自动引用和解引用的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。他是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &、&mut 或 * 以便使 object 与方法签名匹配。也就是说,这些代码是等价的:
代码语言:javascript复制#![allow(unused)]
fn main() {
#[derive(Debug,Copy,Clone)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self, other: &Point) -> f64 {
let x_squared = f64::powi(other.x - self.x, 2);
let y_squared = f64::powi(other.y - self.y, 2);
f64::sqrt(x_squared y_squared)
}
}
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
// 下面这两行等价,因为Rust会帮我们做自动引用和解引用。
p1.distance(&p2);
(&p1).distance(&p2);
}
多个impl定义
每个结构体都允许拥有多个 impl 块。例如:
代码语言:javascript复制struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
此处没有什么实际意义,只是为了说明rust允许这样的语法,在泛型和trait的时候,这一点会变得有用很多。
关联函数
定义在 impl 中且没有 self 的函数被称之为关联函数,它没有self,不能用obj.method的形式调用,而是使用::
的形式来调用,因此他是函数不是方法,而他又在impl中,与结构体紧密关联,因此称为“关联函数”。关联函数在功能上比C 的静态成员函数要灵活的多。
Rust 中有一个约定俗成的规则,使用 new 来作为构造器的名称,出于设计上的考虑,Rust 特地没有用 new 作为关键字
参考资料
- Rust语言圣经
- Rust程序设计语言