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 的枚举功能,为你的工具箱再添一个工具。

咱们下篇见!

相关推荐
神奇小汤圆8 小时前
Java AI 框架选型:LangChain4j 还是 Spring AI?
后端
落羽的落羽8 小时前
【项目】C++从零实现JsonRpc框架——项目引入
linux·服务器·开发语言·c++·人工智能·算法·机器学习
墨月白8 小时前
【Python】程序设计基本方法
开发语言·python
TAN-90°-8 小时前
Java 5——final 抽象 接口
java·开发语言
Andy8 小时前
C++ 容器适配器_栈_队列_双端队列
开发语言·网络·c++
吴声子夜歌8 小时前
Java——显示锁
java·开发语言
思麟呀9 小时前
在C++基础上理解Csharp-2
开发语言·jvm·c++·c#
桀人9 小时前
类和对象——上篇
开发语言·c++
Moment9 小时前
刷 Reddit 1 小时没结果?我用这个方法 10 秒挖出真实需求
前端·javascript·后端
神奇小汤圆9 小时前
小米二面:Redis为什么能支撑10万+QPS?
后端