Rust 方法语法:从定义到实践

Rust 方法语法:


文章目录


1.前言

在 Rust 中,方法 (Method)是定义在结构体、枚举trait 上下文中 的特殊函数。与函数不同,方法的第一个参数总是 self ,代表调用该方法的结构体实例。本文将深入讲解 Rust 方法的定义、调用方式以及核心特性。

Rust 核心设计:数据与行为分离

Rust 中,结构体( struct )只定义数据, impl 块专门用来给结构体实现方法。所有方法都必须写在 impl 里,不能写在 struct 里。这是 Rust 的"数据与行为分离"设计哲学。


2.正文

方法函数 类似, 它们使用 fn 关键字和名称声明, 可以有形参和返回值. 方法和函数是不同的. 因为他们在结构体的上下文中被定义 或者是枚举 或 trait 对象的上下文. 并且他们的第一个参数总是 self, 他们代表该方法的结构体实例.

现在来写一个定义于 Rectangle 结构体上的 area 方法.

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );

}

为了把方法实现在 Rectange 的上下文中, 可以使用 impl 来实现 area 方法.

这里的方法语法就是在 Rectangle 实例上调用 area 方法. 方法语法获取一个实例并加上一个点号, 后面跟上方法名, 圆括号, 以及任何参数.

在 area 的签名中, &self 代表了 rectangle : &Rectangle, &self 实际上是 self : &Self 的缩写. 在 impl 块中, Self 类型是 impl 开的类型的别名. 方法的第一个参数一定要有一个 Self 类型的 self 参数. Rust 可以让你在使用的时候直接使用 self 缩写来实现. 但是这里仍然需要在 self 前面加上 & 来借用 Self 类型的实例. 在上面的代码中没有获取所有权, 但是这里的 self 可以像其他参数一样来获得所有权.

使用方法代替函数, 除了可以使用方法语法不需要在每个函数签名中重复获取 self 的类型, 主要在于组织性.

运算符到哪去了?(引用 wiki 文档)

在 C/C++ 语言中,有两个不同的运算符来调用方法:. 直接在对象上调用方法,而 -> 在一个对象的指针上调用方法,这时需要先解引用(dereference)指针。换句话说,如果 object 是一个指针,那么 object->something() 就像 (*object).something() 一样。

Rust 并没有一个与 -> 等效的运算符;相反,Rust 有一个叫 自动引用和解引用automatic referencing and dereferencing)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。

它是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &&mut* 以便使 object 与方法签名匹配。也就是说,这些代码是等价的:

rust 复制代码
p1.distance(&p2);
(&p1).distance(&p2);

第一行看起来简洁的多。这种自动引用的行为之所以有效,是因为方法有一个明确的接收者------------ self 的类型。在给出接收者和方法名的前提下,Rust 可以明确地计算出方法是仅仅读取(&self),做出修改(&mut self)或者是获取所有权(self)。事实上,Rust 对方法接收者的隐式借用让所有权在实践中更友好。

带有更多参数的方法

如果实现一个方法让一个 Rectangle 的实例获取另一个 Rectangle 实例,如果 self 能完全包含第二个长方形则返回 true;否则返回 false.

rust 复制代码
fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

上面的代码中还没有实现方法.

现在将方法命名为 can_hold (这里就和我阅读教学文档时定义一样吧) . 它应该位于 impl Rectangle 块中. 方法名是 can_hold 并且他会获取另一个不可变的引用作为参数.

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

can_hold 最后返回一个 bool 值. 实现检查 self 的宽度和高度是否都大于另一个 Rectangle.

多个参数举例:

rust 复制代码
impl Rectangle {
    // 两个额外参数
    fn can_hold_with_margin(&self, other: &Rectangle, margin: u32) -> bool {
        self.width > other.width + margin && self.height > other.height + margin
    }

    // 三个额外参数
    fn can_hold_in_position(&self, other: &Rectangle, x: u32, y: u32) -> bool {
        self.width >= other.width + x && self.height >= other.height + y
    }
}

fn main() {
    let rect1 = Rectangle { width: 100, height: 100 };
    let rect2 = Rectangle { width: 30, height: 40 };

    // 调用:self + 2个参数
    println!("{}", rect1.can_hold_with_margin(&rect2, 10)); // true

    // 调用:self + 3个参数
    println!("{}", rect1.can_hold_in_position(&rect2, 20, 30)); // true
}

关联函数

所有在 impl 块中定义的函数被称为关联函数. 可以定义不以 self 为第一参数的关联函数(所以不是一个方法), 应为他们不作用于一个结构体的实例.

关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时作为宽和高,这样可以更轻松的创建一个正方形 Rectangle 而不必指定两次同样的值:

rust 复制代码
impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

使用结构体名和 :: 语法来调用这个关联函数:比如 let sq = Rectangle::square(3);。这个方法位于结构体的命名空间中::: 语法用于关联函数和模块创建的命名空间。

多个 impl

每个结构体都允许拥有多个 impl 块,但每个方法有其自己的 impl 块。

rust 复制代码
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
    }
}

这里没有理由将这些方法分散在多个 impl 块中,不过这是有效的语法。


3.小结

如果这篇文章帮到了你,不妨:

👍 点赞 ------ 让更多人看到这篇教程

⭐ 收藏 ------ 下次直接翻出来看

💬 评论 ------ 遇到任何问题,评论区交流,我会尽力解答

🔖 关注 ------ 结构体并不是创建自定义类型的唯一方法:让我们转向 Rust 的枚举功能,为你的工具箱再添一个工具。

咱们下篇见!

相关推荐
a诠释淡然3 分钟前
C++ vs Rust:哪个更适合你的下一个项目?
开发语言·c++·rust
meilindehuzi_a5 分钟前
深入理解 JavaScript 执行机制:从编译阶段到调用栈底层实现
开发语言·javascript·ecmascript
小小de风呀6 分钟前
de风——【从零开始学C++】(十二):stack和queue的基本使用和模拟实现
开发语言·c++
huohaiyu17 分钟前
深入解析Java垃圾回收机制
java·开发语言·算法·gc
YsyaaabB30 分钟前
LangChain作业二---多语言翻译Prompt
开发语言·python·langchain
JustHappy31 分钟前
古法编程秘籍(五):什么是进程和线程?从软件到 CPU 的一次完整旅程
前端·后端·代码规范
SunnyDays101132 分钟前
如何在 Java 中实现 OFD 与 PDF 格式互转
java·开发语言
BLSxiaopanlaile32 分钟前
关于常见 map的一些比较探究
后端
keykey6.42 分钟前
用 PyTorch 训练图像分类器:完整实战
开发语言·人工智能·深度学习·机器学习
雪度娃娃43 分钟前
转向现代C++——保证const成员函数的线程安全性
开发语言·c++