C++工程师Rust迁移之路- 类与结构体、核心概念Trait
C++类和Rust的结构体之间做一个对比,顺便介绍Rust中的核心概念Trait。
类vs结构体
C++
class Rectangle {
public:
Rectangle(float width, float height)
: width_(width), height_(height)
{}
public:
float area() const {
return width_ * height_;
}
void resize(width, height) {
width_ = width;
height_ = height;
}
private:
float width_, height_;
};
Rust
struct Rectangle {
width: f32,
height: f32
}
impl Rectangle {
pub fn new(width: f32, height: f32) -> Rectangle {
Rectangle {
width: width,
height: height
}
}
pub fn area(&self) -> f32 {
self.width * self.height
}
pub fn resize(&mut self, width: f32, height: f32) {
self.width = width;
self.height = height;
}
}
对于C++开发者来说,有一件事情是众所周知的:struct和class本质上是一样的,唯一的区别就是struct的成员默认是public的,而class的成员默认是private的。
而对于Rust来说,它没有class的概念,只有struct的概念。而Rust中的struct的成员默认都是private的,除非加上pub关键词做修饰。
从语法上来说,可以看到Rust跟C++有一个很大的不同。在C++里面,往往我们的方法声明(对于模版类来说,甚至定义也是如此)是包含在class body这个大的语句块内的,而且对于顺序没有明确的要求。而在Rust里面,结构体的声明仅包含了它内部的数据结构,相关的实现是放在额外的impl语句块中的。这个变化看似稀松平常,但内含着巨大的好处,特别是类的逻辑比较复杂的时候。作为C++程序员,我们一定碰到过那种在一个类的头上和一个类的尾端,甚至是某些函数之间都声明了成员变量的情况。这就引入了一个风险,就是当你修改代码的时候,有可能会忘记给其中的某些变量赋值,这非常危险。而在Rust中,由于结构体的成员变量是与函数分开,并且集中在一起的,就大大降低了出现这种情况的概率。
在C++中,我们写一个类时,第一个要做的事情,就是定义它的构造函数;而反观Rust,它是没有构造函数这个概念的。上述代码中的new函数,如果对比C++的概念的话,相当于Rectangle类的一个静态方法,它的名字叫做new,接受2个f32类型的参数,返回值是一个Rectangle对象。这里有三点需要注意的:
- 构造Rectangle的语法。可以看到,构造Rectangle的时候,直接通过指定它的私有变量的值来实现了构造。而Rust的静态检查器是非常严格的,如果你忘记了构造其中的某个成员变量,它会直接在编译阶段报错,并阻止编译。所以,当你修改了结构体的结构以后,永远不用担心会在某处代码出现未初始化成员变量的bug,因为这样的情况通不过编译。
- 可以看到new函数中,并没有return语句。这是一个非常关键的点。在C++中代码块是一个语句(statement),它是不能作为右值的;而在Rust中,代码块是一个表达式(expression)。所以,在C++中常用的三目运算符([condition] ? [true_exp] : [false_exp])在Rust中并不存在,取而代之的是if表达式(if [condition] { [true_exp] } else { [false_exp] })。
- 另外,我们可以看到,在Rectangle {}的后面是没有分号的,这表示了它是一个表达式,而不是语句。当它不加分号时,这个代码块的类型是Rectangle,而加上了分号以后,它的类型就变成了(),也就是unit type,与函数声明的返回值Rectangle不符,编译出错。关于unit type的更多细节,在以后的文章中,我可以再做进一步的阐述。
我们再接着看area和resize两个方法,可以看到它们的第一个参数分别是&self, 和&mut self。这里其实也有两点需要注意的:
- 与C++一样,&表示引用,这里的&self其实是一个语法糖,相当于self: &Rectangle,而&mut self相当于self: &mut Rectangle
- 就像我在前文中所说,在Rust中,默认的行为是不可变的,除非加上mut关键词。那么在这里的&self,就相当于C++中的const方法,而&mut self相当于非const方法。
Playground
Rust语言的官网提供了一个Playground工具,可以供大家在无需安装Rust环境的前提下,试用Rust。
本文中的例子我也发布到了Rust Playground上,链接如下:
欢迎大家使用。