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

咱们下篇见!

相关推荐
charlie1145141912 小时前
通用GUI编程技术——图形渲染实战(三十七)——D3D11初始化与SwapChain:从零搭建GPU渲染框架
开发语言·c++·3d·图形渲染
陈天伟教授2 小时前
GPT Image 2-城市海报
开发语言·人工智能·gpt·神经网络
原来是猿2 小时前
线程安全的单例模式
linux·服务器·开发语言·单例模式·策略模式
charlie1145141912 小时前
通用GUI编程技术——图形渲染实战(三十六)——Constant Buffer与数据传递:CPU-GPU通信通道
开发语言·c++·windows·c·图形渲染·win32
每天进步一点_JL2 小时前
Java 线程池深度解析:从零开始理解并发编程的核心工具
后端
南境十里·墨染春水2 小时前
C++笔记 STL lterator迭代器
开发语言·c++·笔记
学习使我健康2 小时前
Android 广播介绍详情
android·开发语言·kotlin
lsx2024062 小时前
JavaScript Array(数组)
开发语言
小柯博客2 小时前
Amazon Kinesis Video Streams C WebRTC SDK 开发实战
c语言·开发语言·网络·stm32·嵌入式硬件·webrtc·yocto