找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 207|回复: 0

【Rust】简要描述学习 Rust 的一些心得

[复制链接]
发表于 2025-2-24 17:32:02 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×

简要描述学习 Rust 的一些心得

本文旨在简要描述 Rust 相对于 C++ 的设计上的差异,有 C++ 基础的朋友们应该能看懂。如果你还会 Python 那就更容易了解 Rust 了。

Rust 给我的感觉是「像 Python 那样使用方便,但是静态类型 + RAII,有编译器优化,有完善的库,有包管理器」的编程语言。可以帮助程序员提高生产力,使用更低的思考成本去编写出运行效率更高、更适合团队合作的程序源码。

本文并不会全面介绍 Rust,如果想要全面学习 Rust,请阅读 The Book

如果你没有使用过 C++ 的经验,请勿阅读本文以节省您的时间。

安装 Rust

以下为 Windows 安装 Rust 的方式。

  1. 下载 rustup-init.exe (自己搜)
  2. 准备梯子(没有梯子的话,它的下载速度是 1B/s,四舍五入等于 0B/s)
  3. 启动 Powershell
  1. $proxy='http://<梯子IP>:<梯子端口>'

  2. $ENV:HTTP_PROXY=$proxy
  3. $ENV:HTTPS_PROXY=$proxy
  4. cd ~\Downloads
  5. .\rustup-init.exe
复制代码

然后跟着向导安装就行。Windows 上你需要准备好你的 MSVC,因为 Rust 依赖 MSVC 生成 exe。Linux 上你需要有 gcc。

包管理

Python 有 pip,Rust 有 cargo。

编辑器

我用 Sublime Text。装个 Rust 语法高亮就行。有人会推荐你用 VSCode。看个人习惯,我更习惯 Sublime Text,我写 Python 用的也是这个。

写 Rust 不用像写 C++ 那样高度依赖 VS2022 编辑器。

我的同事认为我太变态了,直接拿简单的编辑器写 Python,没必要。他使用 VSCode 编辑并调试 Python。

变量

请看例子代码,对比 Rust 和 C++ 在定义变量的语法上的差异。

let x = 5; // Rust
const auto x = 5; // C++

let mut y = 5; // Rust
auto y = 5; // C++

let z: u32 = 5; // Rust
const uint32_t z = 5; // C++

Rust 的变量默认不可变,加了 mut 修饰后就可变了。作为对比,C++ 的变量默认可变,加了 const 就不可变了。

Rust 允许重复定义同名的变量,新定义的同名变量会覆盖掉旧的,但是如果新的同名变量没了(比如 Out of scope 了),旧的变量就又能用了。这个叫变量的 Shadowing 概念。

Rust 也有像 C++ 那样的「引用」的概念,用 & 表示引用。但是 Rust 的变量不是你想引用几次就引用几次的,它有条件限制,不过如果你写过正经的 C++,这个限制并不会对你造成什么妨碍,反而会让你舒适。

整数溢出检查

以 Debug 方式编译的 Rust 程序的整数运算会像 VB6 那样,检查整数是否溢出,如果有溢出就会造成 panic,可以使用专门的“允许溢出”的计算方式去计算。而如果是 Release 方式编译的 Rust 程序则不会检查整数溢出。

语句和表达式

Rust 的特性,请看例子代码:

let x =
{
    语句1;
    语句2;
    函数调用();
    返回值 // 注意没有分号
};

它会按顺序运行 语句1; 语句2; 函数调用(); 然后把 返回值 赋给 x
这里面的 {} 块是一个表达式,它是有返回值的(也可以没有)。下面有个更直观的例子,写成一行就是:

let x = {语句1; 语句2; 语句3; 语句4; 返回值};

它会按顺序运行 语句1; 语句2; 语句3; 语句4; 然后把 返回值 赋给 x
相同的规则也可以应用到函数定义上,请看例子代码:

fn foo() -> i32
{
    let a = 40;
    let b = 2;
    a + b
}

这个函数运行后会返回 42。如果你不喜欢这种方式,你也可以用 return,如下:

fn foo() -> i32
{
    let a = 40;
    let b = 2;
    return a + b;
}

如果你曾经是 C 语言的宏孩儿,那这个特性应该能让你爽到。

此外,Rust 有类似于 Python 的一些写法,比如 [1, 2, 3] 是数组,(1, 2, 3)tuple,而 (u32, i32, i8) 这样的则是声明一个 tuple 的每个成员的具体类型。

资源 Ownership

Rust 的资源 Ownership 会让人觉得这是它的一个独特的语法特性,似乎难以理解,但其实如果你知道 C++ 的 std::move(),理解 移动构造,你就比较容易理解 Rust 的 Ownership 概念。

C++ 的 std::move() 是移动语义,比如智能指针的操作,使用移动语义可以把智能指针本身(而不是其指向的内容)移动到另一个智能指针身上。

  • Rust 有 RAII,规则基本上和 C++ 相同。
  • Rust 的结构体有 Copy()Clone() 方法,用于区分一个结构体是否可以用移动语义。如果你实现了 Clone() (深拷贝),那么你的这个结构体就可以用移动语义。
  • Rust 的赋值语句 = 是移动语义,也就是相当于进行一个 C++ 的 std::move() 操作。基本变量类型除外。
  • 同理,Rust 的函数调用,其参数也是这样传递的(移动语义)。函数结束的时候,这个资源被回收。绝大多数情况下是不这样整的,因此通常都是传递引用。

有一种每个优化细节都被把握在自己手掌心上的安全感。
在移动语义这块,C++ 用好 std::move() 可以达到接近的效果。
除了默认的移动语义外,Rust 还有以下的关于引用的限定规则:

  • 有只读引用和可变引用,前者指的是不能修改被引用的变量,后者允许修改被引用的变量。
  • 同一个 Scope 里,对一个变量可以有多个只读引用,但是只能有最多一个可变引用,一旦有了可变引用,就不能有只读引用。

不过实际上规则并没有这么死板,引用只在被你使用的时候(比如作为函数参数传递的时候,或者结构体成员方法会改变结构体自身数据的时候),它检查变量是否符合引用规则。

Rust 编译器在编译期间可以判断每个引用是否有效,任何时候你使用了一个引用了 Out of scope 的变量的时候(最简单的例子,比如函数返回一个 Scope 内的变量的引用),它会报错提示。

字符串或者数组的 Slicing

Rust 的字符串或者数组可以像 Python 那样去取 Slice(视为对字符串或者数组的引用,遵循引用规则)。但是对于字符串,一般不轻易取 Slice,因为 Rust 的字符串是 UTF-8 编码的,但你去 Slice 它的时候,你用的 Index 是基于字节而不是字符的。Rust 认为数组、字符串用 [] 去取其中的元素的这种写法,必须符合 O(1) 复杂度。Python 则不一定,有时候时间复杂度即使是 O(n) ,它也有东西是把接口设计成让你用 [] 去取的。

那如果你真的去 Slice 一个 UTF-8 的字符串,会怎样?Rust 会判断你是不是取完整了一个 UTF-8 字符串,如果取完整了,那就没事;否则它就 panic,提示你截断了 UTF-8 的字符。Rust 的字符串提供了遍历每个 UTF-8 字符的方法。

Rust 的字符串有两个类型,一个是 str,一个是 String,后者拥有一块堆上内存,存储字符串的内容。

Rust 的字符串常量属于 str,而 Rust 的字符串 Slice 也是 str

C++ 没有这么方便的 Slice。C++ 从 UTF-8 字符串里面取出来的是 C++ 的 char。C++ 没有针对 UTF-8、UTF-16、UTF-32 进行互相转换的标准库。惨。

枚举

当你看到以下的代码的时候,你是不是觉得 Rust 的 enum 和 C++ 的几乎是一样的?

enum IpAddrKind
{
    V4,
    V6,
} // 这里没有分号

但是当你看到以下代码的时候,你会懵圈,为啥 Rust 的 enum 还能包含数据类型?
事实上,Rust 的 enum 的每一个枚举项,都可以携带一个属于它的专属的变量类型的数据。

enum IpAddr
{
    V4(String),
    V6(String),
}

或者:

enum IpAddr
{
    V4(u8, u8, u8, u8),
    V6(String),
}

其实,这段代码相当于以下的 C++ 代码:

enum IpAddr_enum
{
    V4,
    V6,
};

struct IpAddr
{
    IpAddr_enum tag;
    union
    {
        uint8_t V4_value[4]; // 对应上面的 (u8, u8, u8, u8),猜猜看我为什么不使用 std::tuple
        std::string V6_value; // 此处假设这个 V6_value 能得到正确的初始化和资源释放
    };
};

Rust 自带的 Option<T> 是一个很有用的枚举,它用于消灭 null。它的定义如下:

enum Option<T> {
    None,
    Some(T),
}

当你的数据有可能是“没有”的状态的时候,也就是你如果有可能需要使用 是否为 null 的写法的时候,这个枚举就派上用场了。使用 match 块(类似于 C++ 的 switch)来判断这个类型的变量,如果是 None 那就没有数据;如果是 Some 那么你可以提取出对应类型的数据。具体看《The Book》的 The match Control Flow Construct 章节,可以在每个 case 的位置用括弧给你的数据定义变量名,然后拿来用。

为什么它被用于“消灭 null”?因为它强制你首先判断一个数据是不是 None,然后才允许你去用这个数据。这种麻烦让你首先就去思考要不要把一个数据整成“可 null 的数据”。

结构体

Rust 的结构体声明方式有很多种,熟悉 C++ 的人应该更适应下面这种:

struct User
{
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

Rust 可以直接用 tuple 的方式声明结构体,比如:

struct Color(i32, i32, i32); // 这种结构体的成员可以用 x.0、x.1、x.2 的方式访问。

Rust 不像 C++ 那样拥有默认构造函数,正常情况下,创建结构体实例的时候必须初始化每一个成员。
Rust 没有结构体继承的概念。
Rust 的结构体可以很方便地调试,使用 dbg!() 可以在运行的时候打印结构体的每个成员名字和值,而且有多种方式控制打印出来的格式。
Rust 结构体方法的定义写在 struct 块的外面,如下:

struct Rectangle
{
    width: u32,
    height: u32,
}

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

看到 &self 了吗?像不像 Python?如果你不会 Python,你可以理解为这个 &self 相当于 C++ 的 *this
一个 impl 块可以包含多个成员方法。Rust 允许你给一个结构体编写多个 impl 块。
构造函数怎么写?参考下面的代码:

impl Rectangle
{
    fn square(size: u32) -> Self
    {
        Self
        {
            width: size,
            height: size,
        }
    }
}

这个函数构造一个正方形的 Rectangle

库与目录结构,命名空间

这块,Rust 像 Python。Python 用 import 引入一个 Python 的库,这个 import 遵循一定的目录结构规则,而 Rust 的 mod 也有差不多的规则,每个模块有它自己的命名空间。

除此以外,在单个源码文件里,Rust 可以使用 mod xxx {} 块来定义命名空间,类似于 C++ 的 namespace {} 块。

Rust 使用双冒号 :: 来访问子命名空间,像 C++。
Rust 可以用 use 来简化命名空间,类似于 C++ 的 using namespace

Rust 使用 Cargo 管理模块,爽。

公共,私有的控制

Rust 用 pub 前缀来修饰模块、结构体、成员方法、函数的公有性。把一个子模块整成公有的以后,外部就能访问你的子模块。结构体被整成公有的以后,外部就能使用你的结构体。把结构体的成员方法整成公有的后,外部就能调用你的结构体成员方法。

Python 使用下划线 _ 前缀修饰模块或者类的成员,使其成为私有的。惨。

数据结构

向量

Rust 有 Vec<T>,约等于 C++ 的 std::vector<T>。在 Rust 引用 Vec<T> 里面的元素的时候,有两种写法:

let v = vec![1, 2, 3, 4, 5];

// 写法1
let third: &i32 = &v[2]; // 如果 index 超出了,它就 panic

// 写法2
let third: Option<&i32> = v.get(2);
// 利用了 Option<T> 的特性,如果 index 超出了,它就返回 None,否则返回 Some(数据)

Rust 可以直接用 for i in &v {} 来遍历 v。写法像 Python。C++ 用 for (auto i: v) {} 来遍历 v。如果要在循环的时候修改数据,Rust 用这种写法:for i in &mut v {*i += 50;} (此处需要解引用 i

哈希表

今天先写到这,等我继续学 Rust。

回复

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2025-3-12 09:27 , Processed in 0.037431 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表