Rust- 基础¶
1 变量与常量¶
变量又分为可变变量和不可变变量;其中不可变变量和常量是不同的。
1.1 变量¶
变量以 let
修饰。
1. Rust 变量声明需要使用 let
关键字修饰
2. 变量的类型可以指定,也可以不指定,类型在变量名之后,如: let x: i32 = 2;
或者 let x = 2;
3. Rust 中变量 默认是不可变的,如果需要可变,需要使用 mut
关键字修饰,如:let mut x = 5;
4. 不可变变量只能有一次赋值,声明和赋值可以分开,如:let a; a=(1,2);
5. 变量赋值时,可以指定类型,比如指定整形是 i32 还是 u64
,如 let x = 2i32
6. 变量赋值时,不指定类型,默认整形是 i32
,浮点型是 f64
7. Rust 中未使用的变量编译时会有警告,如果需要取消警告,可以将变量名以 _
开头,如 let _x = 2;
, 获取使用属性修饰,如 #![allow(dead_code)]
1.2 常量¶
常量以 const
修饰。
#[allow(unused)]
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
const CENTER_POINTS: Point = Point { x: 0, y: 0 };
println!("{:#?}", CENTER_POINTS);
}
1.3 变量遮蔽 (shadowing)¶
Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的,如下所示:
#[allow(unused)]
fn main() {
let x = 5;
// 在main函数的作用域内对之前的x进行遮蔽
let x = x + 1;
{
// 在当前的花括号作用域内,对之前的x进行遮蔽
let x = x * 2;
println!("The value of x in the inner scope is: {}", x);
}
println!("The value of x is: {}", x);
}
// 输出结果:
The value of x in the inner scope is: 12
The value of x is: 6
记住,当是可变变量时
2 类型¶
2.1 基本类型¶
- unit 类型:
()
,这个就相对于什么数据都没有的类型。函数没有返回值时,其实就是返回了()
单元类型。 - char 类型:
char
- 字符串类型:
&str
,字符串字面值是不可变的,因为被硬编码到程序代码中 - bool 类型:
bool
- 浮点型:
f32、f64
- 整形:所有数字字面量允许使用
_
作为可视分隔符,比如:1_234.0E+18f64
,10_000
。
长度 | 有符号类型 | 无符号类型 |
---|---|---|
8 位 | i8 |
u8 |
16 位 | i16 |
u16 |
32 位 | i32 |
u32 |
64 位 | i64 |
u64 |
128 位 | i128 |
u128 |
视架构而定 | isize |
usize |
- 示例
fn main() {
println!("hello world");
let c: char = 'h';
let b: bool = true;
let s: &str = "hello";
let str: String = String::from("hello");//动态字符串类型String
println!("c={} b={} s={} str={}", c, b, s, str);
let i: i32 = 2;
let f: f64 = 2.0;
println!("i={} f={}", i, f);
}
2.2 字符串(切片)¶
rust 中字符串有 2 种,一种是字面量字符串,类型是 &str
,另一种是 String
类型。
- 示例
#![allow(unused)]
fn main() {
let s: &str = "Hello, world!";
}
2.2.1 String¶
String
类型的底层是 [u8]
数组
2.3 数组¶
title: rust数组与C++的不同?
1. rust数组定义语法如:`let a [i32:5] = [1, 2, 3, 4, 5];`,是使用`[]`来赋值,其中`[i32:5]`是`[类型:大小]`可以省略,而C++用`{}`。
2. rust数组不需要在数组名后加`[]`,如C++中是`char a[5];`
3. 数组访问2者是相同的,使用下标访问。
4. rust中函数或方法*参数一般不传数组*(因为不同大小的数组表示的是不同类型),而是传数组切片`&[T]`,而c++数组也相当于一个指针。
数组特点: - 存储在栈上 - 长度固定 - 元素必须有相同的类型 - 依次线性排列 语法示例:
fn main() {
let a1: [i32;5] = [1, 2, 3, 4, 5];
let a2 = [1, 2, 3, 4, 5];
// 3重复5次
let a3 = [3; 5];
let elm = a1[0];
println!("{:?}",a1);
println!("{:?}",a2);
println!("{:?}",a3);
println!("{}",elm);
}
- 可能输出
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[3, 3, 3, 3, 3]
1
2.4 动态数组 Vec¶
- 功能:大小可变的数组,存储在堆上。
- 动态数组 Vector
2.5 切片¶
rust中说的切片其实是**切片引用**,是对*数组的引用*或者*切片的引用*,签名是`&[Type]`,而数组签名是`[Type; Length]`。
字面量字符串其实是一种切片类型,叫做*字符串切片*,签名是`&str`,请注意字符串切片与其他类型切片的不同。
切片(Slice)跟数组相似,但是 切片的长度无法在编译期得知,因此你无法直接使用切片类型;因此我们使用 切片引用来代替切片,实际上 切片也是指切片引用。
- 语法:切片使用序列(Range)语法来确定切片的范围
- let s = &val[start..end]
- let s = &val[start..=end]
- 示例
#![allow(unused)]
fn main() {
let s = String::from("hello");
let len = s.len();
// 字符串是切片,类型是&str
let slice1 : &str = &s[0..len];
// 对切片再次切片
let slice2 = &slice1[..2];
println!("{:?}",s);
println!("{:?}",slice1);
println!("{:?}",slice2);
let arr = [1, 2, 3];
// i32切片
let s1: &[i32] = &arr[0..=2];
println!("{:?}",s1);
}
- 可能的输出
"hello"
"hello"
"he"
[1, 2, 3]
2.6 元组¶
rust 中元组的签名是 (Type1,Type2,...,Typen)
元组中每一个元素可以是不同类型。
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
可以通过索引访问元组中的数据:
fn main() {
let tp:(&str, i32, &str) = ("hello",1,"world");
println!("{:?}",tp);
println!("0:{},1:{},2:{}",tp.0,tp.1,tp.2);
}
// 输出结果
("hello", 1, "world")
0:hello,1:1,2:world
或者解构后使用:
#[allow(unused)]
fn main() {
let tp: (&str, i32, &str) = ("hello", 1, "world");
let (x, y, z) = tp;
println!("{:?}", tp);
println!("0:{},1:{},2:{}", x, y, z);
}
// 输出结果
("hello", 1, "world")
0:hello,1:1,2:world
2.7 结构体¶
1. rust 中结构体和 C 结构体有点类似,有成员变量,不存在C++中的构造、析构函数。
2. rust 结构体也可以有方法(使用`impl`关键字声明),但是和C++类不同的是,rust*方法和数据是分离*的,不是在同一处定义。
3. 和C++一样,允许方法和字段(成员变量)同名。
rust 中结构体特点:
1. 和 C 一样使用 struct
关键字标识
2. 可以有多个成员变量
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
rust 结构体方法与其它语言的对比:
2.7.1 创建结构体¶
1. rust中初始化和C++一样,都可以使用花括号`{}`初始化
2. C++20开始支持指定初始化,但需要按成员变量声明顺序进行,而rust中不需要。
- 初始化实例时,每个字段 都需要进行初始化
- 初始化时的字段顺序 不需要 和结构体定义时的顺序一致
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
2.7.2 使用结构体¶
rust和C++中一致,使用`.`来访问成员变量
#![allow(unused)]
fn main() {
// 默认变量是不可变的,因此想要修改变量,必须声明mut
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
2.7.3 更新结构体¶
结构体变量赋值,是**所有权的转让**,因此除了基本类型外的其他类型,在赋值结束后是不能再次使用的。但没有被**转移的字段依然可以使用**,比如下面示例中`user2.email`可以使用,但`user2.username`不能使用。
rust 中有需要很方便的设计来更新结构体成员变量的值。
当从一个结构体给另一个结构体赋值时,可以使用 ..变量名
的方式,但必须在 结构体的尾部 使用
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
let user3 = User{
email: String::from("hello@example.com"),
..user2 //必须在结构体尾部
};
println!("{:?}",user3);
}
2.7.4 元组结构体 (Tuple Struct)¶
结构体必须要有名称,但是结构体的字段可以没有名称,这种 结构体长得很像元组,因此被称为元组结构体,例如:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。例如上面的 Point
元组结构体,众所周知 3D 点是 (x, y, z)
形式的坐标点,因此我们无需再为内部的字段逐一命名为:x
, y
, z
。
2.7.5 单元结构体 (Unit-like Struct)¶
单元结构体 和 单元类型 很像,没有任何字段和属性,如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体:
struct AlwaysEqual;
let subject = AlwaysEqual;
// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {
}
2.8 枚举¶
rust中枚举很强大,不仅结合了C++中的限域枚举`enum class`风格,还结合了结构体风格,**每一个枚举值都可以有不同的类型**,甚至可以**存储值**。
2.8.1 无类型的枚举值¶
#[derive(Debug)]
enum PokerSuit {
Clubs,
Spades,
Diamonds,
Hearts,
}
fn main() {
let heart = PokerSuit::Hearts;
let diamond = PokerSuit::Diamonds;
println!("{:?}", heart);
println!("{:?}", diamond);
}
- 可能的输出
Hearts
Diamonds
2.8.2 有类型的枚举值¶
枚举值的类型可以是任意类型,指定类型时的方式却并不一样,如:
- 元组:`(T1,T2,..,Tn)`
- 匿名结构体:{v1:t1,..,vn:tn}
- 其它类型:`(T)`
- 示例
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m1 = Message::Quit;
let m2 = Message::Move{x:1,y:1};
let m3 = Message::ChangeColor(255,255,0);
}
该枚举类型代表一条消息,它包含四个不同的成员:
- Quit
没有任何关联数据
- Move
包含一个匿名结构体
- Write
包含一个 String
字符串
- ChangeColor
包含三个 i32
其它示例:
#![allow(unused)]
#[derive(Debug)]
enum PokerCard {
Clubs(u8),
Spades(u8),
Diamonds(char),
Hearts(char),
}
fn main() {
let c1 = PokerCard::Spades(5);
let c2 = PokerCard::Diamonds('A');
println!("{:?}",c1);
println!("{:?}",c2);
}
- 可能的输出
Spades(5)
Diamonds('A')
2.9 函数指针¶
Rust 种使用 fn
关键字来声明函数指针。
1. 声明函数: 使用 fn
关键字可以声明一个函数。例如:
fn add(x: i32, y: i32) -> i32 { x + y }
- 声明函数指针类型:
fn
关键字还可用于声明函数指针类型。
fn add_one(x: usize) -> usize {
x + 1
}
let ptr: fn(usize) -> usize = add_one;
assert_eq!(ptr(5), 6);
let clos: fn(usize) -> usize = |x| x + 5;
assert_eq!(clos(5), 10);
- 当然也可以使用 type 关键字为函数指针声明别名,这样更方便使用:
type MyFn = fn(i32, i32) -> i32;
let ptr1: MyFn = |x, y| x + y;
let ptr2: MyFn = |x, y| x - y;
let res1 = ptr1(5, 2);
let res2 = ptr2(5, 2);
这里 MyFn
是一个函数指针类型,表示接受两个 i32
参数并返回一个 i32
值的函数。
4. 使用函数指针: 函数指针可以用于传递函数地址,或者作为某些数据结构的字段。例如:
fn apply_function(f: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {
f(x, y)
}
let result = apply_function(add, 3, 4);
这里 apply_function
函数接受一个函数指针 f
,以及两个参数 x
和 y
,然后调用函数指针来执行相应的函数。
5. 使用闭包和 fn
混合: fn
关键字和闭包可以混合使用。函数指针和闭包都可以作为函数参数或返回值。例如:
fn operate(op: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {
op(x, y)
}
let add_result = operate(add, 3, 4);
let multiply_result = operate(|a, b| a * b, 3, 4);
这里 operate
函数接受一个函数指针或闭包,并调用它来执行相应的操作。
2.9.1 安全性¶
除了根据其签名而有所不同外,函数指针还具有两种形式:安全和不安全。 普通 fn()
函数指针只能指向安全函数,而 unsafe fn()
函数指针可以指向安全或不安全函数。
fn add_one(x: usize) -> usize {
x + 1
}
unsafe fn add_one_unsafely(x: usize) -> usize {
x + 1
}
let safe_ptr: fn(usize) -> usize = add_one;
// ERROR: 不匹配的类型:预期正常 fn,发现不安全 fn let bad_ptr: fn(usize) -> usize=add_one_unsafely;
let unsafe_ptr: unsafe fn(usize) -> usize = add_one_unsafely;
let really_safe_ptr: unsafe fn(usize) -> usize = add_one;
3 解构¶
**解构也是通过模式匹配规则**来执行的。解构支持*结构体、枚举、元组、数组和引用*。
- 解构示例 常用的几种如下
struct Struct {
e: i32
}
fn main() {
let (a, b, c, d, e);
(a, b) = (1, 2);
// _ 代表匹配一个值,但是我们不关心具体的值是什么,因此没有使用一个变量名而是使用了 _
[c, .., d, _] = [1, 2, 3, 4, 5];
Struct { e, .. } = Struct { e: 5 };
assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
}
4 impl 方法¶
1. rust中方法对应C++中的成员函数,也是和一个对象关联的。
2. rust中方法是通过关键字`impl`来定义的。
3. Rust 允许我们为一个结构体定义多个 `impl` 块
4. 方法中的`self`相当于C++中的`this`,表示当前对象。
- 示例
#![allow(unused)]
fn main() {
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
// new是Circle的关联函数,因为它的第一个参数不是self,且new并不是关键字
// 这种方法往往用于初始化当前结构体的实例
fn new(x: f64, y: f64, radius: f64) -> Circle {
Circle {
x: x,
y: y,
radius: radius,
}
}
// Circle的方法,&self表示借用当前的Circle结构体
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
}
4.1 self¶
`&self` 其实是 `self: &Self` 的简写(注意大小写)
self
关键字有 3 种形式:
1. self
:表示 所有权转移 到该方法中(这种形式用的较少,使用后导致调用方不再拥有所有权了,对象将释放)
2. &self
:表示该方法对此 对象的不可变引用
3. &mut self
:表示该方法对此 对象的可变引用
4.2 方法的调用¶
rust 种方法通过 对象.方法
的形式来使用,这种形式和 C++ 中相同;而 C++ 中还有指针通过 指针->方法
或者 (*指针).方法
的方式;rust 简化了这种场景,不管方法是 self
、&self
、&mut self
都只需要 对象.方法
形式访问。
- 示例
#![allow(unused)]
fn main() {
#[derive(Debug,Copy,Clone)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self, other: &Point) -> f64 {
let x_squared = f64::powi(other.x - self.x, 2);
let y_squared = f64::powi(other.y - self.y, 2);
f64::sqrt(x_squared + y_squared)
}
}
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
// 以下2种调用时等价的
p1.distance(&p2);
(&p1).distance(&p2);
}
4.3 关联函数¶
rust中结构体关联函数和C++中static类成员函数相同,访问方式也时`结构体::方法`的形式。
这种定义在 impl
中且没有 self
的函数被称之为 关联函数: 因为它没有 self
,不能用 f.read()
的形式调用,因此它是一个函数而不是方法,它又在 impl
中,与结构体紧密关联,因此称为关联函数。
在之前的代码中,我们已经多次使用过关联函数,例如 String::from
,用于创建一个动态字符串。
impl Rectangle {
fn new(w: u32, h: u32) -> Rectangle {
Rectangle { width: w, height: h }
}
}
Rust 中有一个约定俗成的规则,使用
new
来作为构造器的名称,出于设计上的考虑,Rust 特地没有用new
作为关键字
因为是函数,所以不能用 .
的方式来调用,我们需要用 ::
来调用,例如 let sq = Rectangle::new(3, 3);
。这个方法位于结构体的命名空间中:::
语法用于关联函数和模块创建的命名空间。
4.4 为枚举实现方法¶
枚举类型之所以强大,不仅仅在于它好用、可以 同一化类型,还在于,我们可以像结构体一样,为枚举实现方法:
#![allow(unused)]
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// 在这里定义方法体
println!("{:?}:",self);
if let Message::Write(str) = self {
println!("str={}", str.to_string())
}
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
let m = Message::Move{x:2,y:3};
m.call();
}
// 输出结果:
Write("hello"):
str=hello
Move { x: 2, y: 3 }:
5 函数¶
rust中函数指的是**不和结构体关联**的函数。对应的是C++中非类成员函数。
rust中可以省略return关键字,默认将最后一个表达式的值返回。
- 函数结构:
fn func_name(param: type,...)->return_type{statment}
- 无返回值时,
->return_type
可以不写,或者是->()
- 和 C++ 一样,使用
return
关键字返回,也可以不使用。 - 和 C++ 一样,参数必须指定类型
- 参数类型和变量声明一样,也是放在名称后面
- rust 中函数可以放在任意位置,而 C++ 中要求使用的函数需要在当前位置之前出现,或者有函数声明。
- 无返回值时,
- 示例
fn sum(a: i64, b: i64) -> i64 {
return a + b;
}
fn main() {
println!("sum={}", sum(1, 2));
}
5.1 发散函数¶
发散函数是 永远不会返回 的函数,这个和没有返回值是不一样的。使用 !
标记返回类型
- 示例
fn foo() -> ! {
panic!("This call never returns.");
}
6 闭包 closure¶
rust中的闭包相当于C++中的Lambda表达式。
闭包是 一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值。
fn main() {
let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));
}
上面示例中闭包捕获了变量 x。
如果尝试调用闭包两次,第一次使用 String
类型作为参数而第二次使用 u32
,则会得到一个错误:
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
编译报错内容:
error[E0308]: mismatched types
--> src/main.rs
|
| let n = example_closure(5);
| ^ expected struct `std::string::String`, found
integer
|
= note: expected type `std::string::String`
found type `{integer}`
第一次使用 String
值调用 example_closure
时,编译器推断 x
和此闭包返回值的类型为 String
。接着这些类型被锁定进闭包 example_closure
中,如果尝试对同一闭包使用不同类型则会得到类型错误。
6.1 语法¶
这些都是有效的闭包定义,并在调用时产生相同的行为。参数和返回值类型标注是可选的,闭包定义会为每个参数和返回值推断一个具体类型。
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
- 第一行展示了一个函数定义,用 fn 去承接,因此尾部不需要分号。
- 第二行展示了一个完整标注的闭包定义。
- 第三行闭包定义中省略了类型标注。
- 第四行去掉了可选的大括号,因为闭包体只有一行。
6.2 move 闭包¶
move 关键字能将闭包内使用的外部变量所有权转移到闭包内,这样,在闭包定义之后就不能使用这个变量了。
#![allow(unused)]
use std::ops::DerefMut;
fn main() {
let x: Vec<i32> = vec![1, 2, 3 ,4];
let equal_to_x = move|z: &mut Vec<i32>| {z.push(4); z.deref_mut() == x};
//println!("can't use x here: {:?}", x);
let mut y: Vec<i32> = vec![1, 2, 3];
assert!(equal_to_x(&mut y));
}
7 生命周期¶
7.1 所有权 ownership¶
Rust**通过所有权来管理内存**。和 C++ 中 **手动管理内存的分配和释放** 不同。
7.1.1 所有权原则¶
- Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
- 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
- 当所有者 (变量) 离开作用域范围时,这个值将被丢弃 (drop)
7.1.2 所有权转移¶
- 在进行赋值(let x = y)或通过值来传递函数参数(foo(x))的时候,资源的所有权(ownership)会发生转移。按照 Rust 的说法,这被称为资源的移动(move)。
- 注意:引用并不会发生所有权的转移。
- 基本类型间赋值,默认是通过 copy 来赋值的,不会有所有权的转移。因此赋值后依然可以访问。
- 其他类型的变量赋值(
let a = b
),默认是将所有权从 a 转移到 b,因此,赋值结束后,a 就不能再访问了。如果通过深拷贝的方式来赋值,也不会有所有权的转移。 - 通过值来传递函数参数(
foo(x)
)(注意不是引用) 时, x 的所有权被转移到函数内部,在调用函数之后,x 将不能使用。
// 此函数取得堆分配的内存的所有权
fn destroy_box(c: Box<i32>) {
println!("Destroying a box that contains {}", c);
// `c` 被销毁且内存得到释放
}
fn main() {
// 栈分配的整型
let x = 5u32;
// 将 `x` *复制*到 `y`——不存在资源移动
let y = x;
// x y 是基本类型,是copy,因此两个值各自都可以使用
println!("x is {}, and y is {}", x, y);
// `a` 是一个指向堆分配的整数的指针
let a = Box::new(5i32);
println!("a contains: {}", a);
// *移动* `a` 到 `b`
let b = a;
// 把 `a` 的指针地址(而非数据)复制到 `b`。现在两者都指向
// 同一个堆分配的数据,但是现在是 `b` 拥有它。
// 报错!`a` 不能访问数据,因为它不再拥有那部分堆上的内存。
//println!("a contains: {}", a);
// 试一试 ^ 去掉此行注释
// 此函数从 `b` 中取得堆分配的内存的所有权
destroy_box(b);
// 此时堆内存已经被释放,这个操作会导致解引用已释放的内存,而这是编译器禁止的。
// 报错!和前面出错的原因一样。
//println!("b contains: {}", b);
// 试一试 ^ 去掉此行注释
}
7.2 引用¶
引用也被称为借用(borrow),机制可以让所有权转移实现的变量传递变得简洁。
**切片和字符串字面量或者字符串切片是一种引用类型。**
rust 引用类似于 C++ 中变量的指针和引用的结合体(虽然叫引用,但需要解引用,就形同 C++ 中解指针一样)。
编译器(通过借用检查)静态地保证了引用 总是 指向有效的对象。也就是说,当存在引用指向一个对象时,该对象不能被销毁。
// 此函数取得一个 box 的所有权并销毁它
fn eat_box_i32(boxed_i32: Box<i32>) {
println!("Destroying box that contains {}", boxed_i32);
}
// 此函数借用了一个 i32 类型
fn borrow_i32(borrowed_i32: &i32) {
println!("This int is: {}", borrowed_i32);
}
fn main() {
// 创建一个装箱的 i32 类型,以及一个存在栈中的 i32 类型。
let boxed_i32 = Box::new(5_i32);
let stacked_i32 = 6_i32;
// 借用了 box 的内容,但没有取得所有权,所以 box 的内容之后可以再次借用。
// 译注:请注意函数自身就是一个作用域,因此下面两个函数运行完成以后,
// 在函数中临时创建的引用也就不复存在了。
borrow_i32(&boxed_i32);
borrow_i32(&stacked_i32);
{
// 取得一个对 box 中数据的引用
let _ref_to_i32: &i32 = &boxed_i32;
// 报错!
// 当 `boxed_i32` 里面的值之后在作用域中被借用时,不能将其销毁。
eat_box_i32(boxed_i32);
// 改正 ^ 注释掉此行
// 在 `_ref_to_i32` 里面的值被销毁后,尝试借用 `_ref_to_i32`
//(译注:如果此处不借用,则在上一行的代码中,eat_box_i32(boxed_i32)可以将 `boxed_i32` 销毁。)
borrow_i32(_ref_to_i32);
// `_ref_to_i32` 离开作用域且不再被借用。
}
// `boxed_i32` 现在可以将所有权交给 `eat_i32` 并被销毁。
//(译注:能够销毁是因为已经不存在对 `boxed_i32` 的引用)
eat_box_i32(boxed_i32);
}
7.2.1 解引用¶
用 &变量
引用,用 *变量
解引用:
fn main() {
let x: i32 = 5;
let y: &i32 = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
注意,在函数参数定义引用变量时,使用不需要解引用:
// 此函数借用了一个 i32 类型
fn borrow_i32(borrowed_i32: &i32) {
println!("This int is: {}", borrowed_i32);
}
7.2.2 不可变引用¶
rust 默认的引用是不可变的,即你无法修改引用的数据,相当于 C++ 中 const int * pi=2;
,不能修改 pi
的值。
7.2.3 可变引用¶
要想使用可变引用只需加上 mut
关键字即可。可变引用会借用引用的数据,当可变引用不再使用时,就归还了。
可变引用的限制如下:
1. 只能有一个可变引用
2. 可变引用不能和不可变引用同时存在。
- 示例
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
修改可变引用的数据:
#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
let mut a = Point { x: 1, y: 2 };
println!("{a:?}");
// b可变引用a,这样才能通过b修改a中数据
let b = &mut a;
b.x = 3;
println!("{a:?}");
// 首先c是可变的,其次可变引用a,这样才能修改c
let mut c = &mut a;
*c = Point { x: 3, y: 5 };
println!("{a:?}");
}
// 输出结果
Point { x: 1, y: 2 }
Point { x: 3, y: 2 }
Point { x: 3, y: 5 }
同一时间内只允许 一次 可变借用。仅当最后一次使用可变引用 之后,原始数据才可以再次借用。
struct Point { x: i32, y: i32, z: i32 }
fn main() {
let mut point = Point { x: 0, y: 0, z: 0 };
let borrowed_point = &point;
let another_borrow = &point;
// 数据可以通过引用或原始类型来访问
println!("Point has coordinates: ({}, {}, {})",
borrowed_point.x, another_borrow.y, point.z);
// 报错!`point` 不能以可变方式借用,因为当前还有不可变借用。
// let mutable_borrow = &mut point;
// TODO ^ 试一试去掉此行注释
// 被借用的值在这里被重新使用
println!("Point has coordinates: ({}, {}, {})",
borrowed_point.x, another_borrow.y, point.z);
// 不可变的引用不再用于其余的代码,因此可以使用可变的引用重新借用。
let mutable_borrow = &mut point;
// 通过可变引用来修改数据
mutable_borrow.x = 5;
mutable_borrow.y = 2;
mutable_borrow.z = 1;
// 报错!不能再以不可变方式来借用 `point`,因为它当前已经被可变借用。
// let y = &point.y;
// TODO ^ 试一试去掉此行注释
// 报错!无法打印,因为 `println!` 用到了一个不可变引用。
// println!("Point Z coordinate is {}", point.z);
// TODO ^ 试一试去掉此行注释
// 正常运行!可变引用能够以不可变类型传入 `println!`
println!("Point has coordinates: ({}, {}, {})",
mutable_borrow.x, mutable_borrow.y, mutable_borrow.z);
// 可变引用不再用于其余的代码,因此可以重新借用
let new_borrowed_point = &point;
println!("Point now has coordinates: ({}, {}, {})",
new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z);
}
7.2.4 悬垂引用 (Dangling References)¶
rust 悬垂引用和 C++ 悬垂指针一样,不能返回一个局部变量的引用。 - 示例
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s //出了函数,s被释放,变悬垂引用
}
7.3 可变性¶
rust 中默认变量是不可变的,如 let a =1u32;
,此处 a 是只读的,不能被修改。当使用 mut
修饰时,此时变量就是可变的,具有外部可见性。
7.3.1 外部可变性¶
用 mut
修饰的变量具有外部可见性。
7.3.2 内部可变性¶
内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe
代码来模糊 Rust 通常的可变性和借用规则。简单来说,在不可变值内部改变值就是 内部可变性 模式。
可以使用 RefCell<T>
来声明内部可变性。
fn main() {
use std::cell::RefCell;
#[derive(Debug)]
pub struct Foo {
i: RefCell<u32>,
}
impl Foo {
pub fn new() -> Self {
Self { i: RefCell::new(0) }
}
pub fn set_1(&mut self, v: u32) {
*self.i.borrow_mut() = v;
}
pub fn set_2(&self, v: u32) {
*self.i.borrow_mut() = v;
}
}
let mut f1 = Foo::new();
// 只能用可变变量f1调用set_1,不可变变量f2调用会报错
f1.set_1(1);
let f2 = Foo::new();
f2.set_2(2);
println!("{f1:?}");
println!("{f2:?}");
}
// 输出结果
Foo { i: RefCell { value: 2 } }
Foo { i: RefCell { value: 3 } }
7.4 生命周期¶
https://course.rs/basic/lifetime.html#%E7%BB%93%E6%9E%84%E4%BD%93%E4%B8%AD%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F
8 流程控制¶
`if、for、while、loop`与C++语法上最大区别是:条件表达式不需要`()`包裹。
Rust 也有`break、continue`关键字,功能和C++完全一致,但break可以**带一个返回值**,有些类似 `return`。
8.1 if¶
Rust if 和C++中很像,都有`if、else、else if`这3种判断条件。但有2点不同:
1. Rust条件表达式不需要用`()`包裹。
2. Rust if语句是表达式,可以返回结果,但各分支返回值必须相同;而C++中不能。
- 示例
#![allow(unused)]
fn main() {
let number = 6;
let s = if number % 4 == 0 {
"number is divisible by 4"
} else if number % 3 == 0 {
"number is divisible by 3"
} else if number % 2 == 0 {
"number is divisible by 2"
} else {
"number is not divisible by 4, 3, or 2"
};
println!("{}",s);
}
- 可能的输出
number is divisible by 3
8.2 if let¶
有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用 match
来处理就要写成下面这样:
let v = Some(3u8);
match v {
Some(3) => println!("three"),
_ => (),
}
使用 if let
如下:
#![allow(unused)]
fn main() {
let v = Some(3u8);
// if 也可以(注意后面的==号)
if Some(3) == v {
println!("three 1");
}
// if let 也可以,(注意后面的=号)
if let Some(3) = v {
println!("three 2");
}
}
从上面代码能看出,有些场景下使用 if let 和 if
作用相同,但有些场景 (比如变量覆盖) 就不行,如下
fn main() {
let age = Some(30);
println!("在匹配前,age是{:?}",age);
// 此代码不通过,需要使用`if let Some(age) = age`
/*if Some(age) == age {
println!("匹配出来的age是{}",age);
}*/
// if let 可以
if let Some(age) = age {
println!("匹配出来的age是{}",age);
}
println!("在匹配后,age是{:?}",age);
}
- 可能的输出
在匹配前,age是Some(30)
匹配出来的age是30
在匹配后,age是Some(30)
8.3 for¶
Rust 中 for 语法有 3 种格式,如下:
1. 循环集合,只关注值,如 for 元素 in 集合 {//表达式或语句}
2. 循环集合,同时关注索引、值,如:for (index, value) in 集合 {//表达式或语句}
3. 循环集合,忽略任何值、索引,如:for _ in 集合 {//表达式或语句}
- 示例
fn main() {
for i in 1..=5 {
println!("{}", i);
}
}
- 可能的输出
1
2
3
4
5
for 循环中也能使用可变 mut
、引用 &
语法,总结如下:
使用方法 | 等价使用方式 | 所有权 |
---|---|---|
for item in collection |
for item in IntoIterator::into_iter(collection) |
转移所有权 |
for item in &collection |
for item in collection.iter() |
不可变借用 |
for item in &mut collection |
for item in collection.iter_mut() |
可变借用 |
8.4 while¶
Rust中while和C++中while一样。
- 示例
fn main() {
let mut n = 0;
while n <= 5 {
println!("{}!", n);
n = n + 1;
}
println!("我出来了!");
}
8.5 while let¶
和 if let
相似
- 示例
#![allow(unused)]
fn main() {
// Vec是动态数组
let mut stack = Vec::new();
// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);
// stack.pop从数组尾部弹出元素
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
- 可能的输出
3
2
1
8.6 loop¶
loop 有点类似与 while 1
,循环一直进行,除非循环体主动 break
或 return
。loop 还是一个表达式,因此可以返回一个值。
- 示例
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
- 可能的输出
The result is 20
8.7 break、continue¶
Rust中break、continue功能和C++只能一样,但break功能更强,**可以单独使用,也可以带一个返回值**,有些类似 `return`
9 模式匹配¶
9.1 可反驳的(refutable)和不可反驳的(irrefutable)¶
模式有两种形式:可反驳的(refutable)和不可反驳的(irrefutable)。
- 不可反驳的(irrefutable):能 匹配任何可能值 的模式是不可反驳的。如 let x = 5;
中的 x,因为 x 可以匹配任何值所以不可能匹配失败。
- 可反驳的(refutable):对于可能的值,存在匹配失败 的情况的模式是可反驳的。如,表达式 if let Some (x) = a_value 中的 Some (x)
,如果变量 a_value 中的值是 None 而不是 Some,那么与模式 Some (x) 就会匹配失败。
9.2 match¶
1. rust中的match功能上和C++中的`switch case`一样,但语法格式完全不一样。
2. rust中match是一个表达式,因此match分支需要返回一个值。
match 语法格式如下:
match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}
有以下几点值得注意:
1. match
的匹配必须要穷举出所有可能,因此这里用 _
来代表未列出的所有可能性
2. match
的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
3. X | Y
,类似逻辑运算符 或
,代表该分支可以匹配 X
也可以匹配 Y
,只要满足一个即可。
4. 当 match
的是数值类型时还可以使用 1..=5
表示匹配数值 1~5
。
- 示例:请注意 println!
宏展开后其实也是个表达式。
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
_ => println!("West"),
};
}
9.2.1 模式绑定¶
在匹配有类型的枚举时,可以从枚举值中取出值。 - 示例
enum Action {
Say(String),
MoveTo(i32, i32),
ChangeColorRGB(u16, u16, u16),
}
fn main() {
let actions = [
Action::Say("Hello Rust".to_string()),
Action::MoveTo(1,2),
Action::ChangeColorRGB(255,255,0),
];
for action in actions {
match action {
Action::Say(s) => {
println!("{}", s);
},
Action::MoveTo(x, y) => {
println!("point from (0, 0) move to ({}, {})", x, y);
},
Action::ChangeColorRGB(r, g, _) => {
println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
r, g,
);
}
}
}
}
9.2.2 匹配守卫(match guard)¶
在 match
分支模式之后的增加一个额外 if
条件,当所有条件满足时就匹配
#[allow(unused)]
fn main() {
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}
9.2.3 通过序列 ..=
匹配值的范围¶
..=
语法允许你匹配一个闭区间序列内的值。
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
如果 x
是 1、2、3、4 或 5,第一个分支就会匹配。这相比使用 |
运算符表达相同的意思更为方便;相比 1..=5
,使用 |
则不得不指定 1 | 2 | 3 | 4 | 5
这五个值,而使用 ..=
指定序列就简短的多,比如希望匹配比如从 1 到 1000 的数字的时候!
序列只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型。
如下是一个使用字符类型序列的例子:
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
9.3 @绑定¶
@
(读作 at)运算符允许为一个字段绑定另外一个变量。下面例子中,我们希望测试 Message::Hello
的 id
字段是否位于 3..=7
范围内,同时也希望能将其值绑定到 id_variable
变量中以便此分支中相关的代码可以使用它。我们可以将 id_variable
命名为 id
,与字段同名,不过出于示例的目的这里选择了不同的名称。
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("Found an id in range: {}", id_variable)
},
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
},
Message::Hello { id } => {
println!("Found some other id: {}", id)
},
}
上例会打印出 Found an id in range: 5
。通过在 3..=7
之前指定 id_variable @
,我们捕获了任何匹配此范围的值并同时将该值绑定到变量 id_variable
上。
第二个分支只在模式中指定了一个范围,id
字段的值可以是 10、11 或 12
,不过这个模式的代码并不知情也不能使用 id
字段中的值,因为没有将 id
值保存进一个变量。
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 id
,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 id
字段的值进行测试:任何值都会匹配此分支。
当你既想要限定分支范围,又想要使用分支的变量时,就可以用 @
来绑定到一个新的变量上,实现想要的功能。
9.3.1 @前绑定后解构 (Rust 1.56 新增)¶
使用 @
还可以在绑定新变量的同时,对目标进行解构:
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
// 绑定新变量 `p`,同时对 `Point` 进行解构
let p @ Point {x: px, y: py } = Point {x: 10, y: 23};
println!("x: {}, y: {}", px, py);
println!("{:?}", p);
let point = Point {x: 10, y: 5};
if let p @ Point {x: 10, y} = point {
println!("x is 10 and y is {} in {:?}", y, p);
} else {
println!("x was not 10 :(");
}
}
9.3.2 @新特性 (Rust 1.53 新增)¶
考虑下面一段代码:
fn main() {
match 1 {
num @ 1 | 2 => {
println!("{}", num);
}
_ => {}
}
}
编译不通过,是因为 num
没有绑定到所有的模式上,只绑定了模式 1
,你可能会试图通过这个方式来解决:
num @ (1 | 2)
但是,如果你用的是 Rust 1.53 之前的版本,那这种写法会报错,因为编译器不支持。
10 格式化输出¶
10.1 格式化输出宏¶
https://course.rs/basic/formatted-output.html
`print!、println!、format!`其实是一个*表达式*。
print!
: 将格式化文本输出到标准输出,不带换行符println!
: 同上,但是在行的末尾添加换行符format!
: 将格式化文本输出到String
字符串 以上 3 个,又支持 3 种占位标志:{}
:适用于实现了std::fmt::Display
特征的类型{:?}
:适用于实现了std::fmt::Debug
特征的类型,在结构体前声明#[derive(Debug)]
,就可以不自定义格式化器。{:#?}
:和{:?}
类似,但输出格式不同
#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 10, y: 11 };
let px: i32 = p.x;
println!("{:#?}", p);
println!("{:?}", p);
println!("{p:?}");
}
// 输出结果:
Point {
x: 10,
y: 11,
}
Point { x: 10, y: 11 }
Point { x: 10, y: 11 }
10.2 自定义输出格式¶
只要为类型实现 Display
特征,就能控制输出格式
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
// 输出结果
// w = [hello, world]
11 预导入包¶
https://www.rustwiki.org.cn/zh-CN/reference/names/preludes.html https://www.rustwiki.org.cn/zh-CN/std/prelude/index.html
12 增强功能¶
12.1 序列 (Range)¶
Range 用来生成连续的数值。
- 语法格式:
- num_start..num_end
:从 num_start 到 num_end-1 的连续数字,例如 1..5
,生成从 1 到 4 的连续数字
- num_start..=num_end
:从 num_start 到 num_end 的连续数字,1..=5
,生成从 1 到 5 的连续数字
- 示例
fn fun1(){
for i in 0..=5{
println!("{}",i);
}
}
fn main() {
fun1();
}
- 可能结果
0
1
2
3
4
5
12.2 NaN¶
- 功能:对于数学上未定义的结果,例如对负数取平方根
-42.1.sqrt()
,会产生一个特殊的结果:Rust 的浮点数类型使用NaN
(not a number) 来处理这些情况。 - 判断数值是否合规:使用
is_nan()
函数 - 示例
fn main() {
let x = (-42.0_f32).sqrt();
if x.is_nan() {
println!("未定义的数学行为")
}
}
- 可能的输出
未定义的数学行为
12.3 通配符 _¶
在 rust 中 _
符号是一个统配符,表示可以匹配任意内容,通常可以用在一下几处:
1. match
表达式结尾分支,当前面分支没有匹配时,_
就会匹配任意内容。
2. for in
循环中,当我们不关心值时,用 _
忽略值
3. _
甚至可以作为变量名前缀,表示未使用的变量,编译不会警告。
- 示例 1
for _ in 0..10 {
// ...
}
- 示例 2
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
_ => println!("West"),
};
}
12.4 忽略符..¶
..
可以出现在序列(Range)中,也可以用于忽略匹配值,忽略规则如下:
1. 忽略前值或后值
2. 忽略中间值
3. ..
必须是无歧义的
- 忽略前、后值:
#![allow(unused)]
fn main() {
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
}
- 忽略中间值:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
},
}
}
- 有歧义的,编译失败:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
12.5 Result 和?¶
? 是一种针对 Result 类型做 match 操作的语法糖。如果结果是 Ok(T)
,则把 T
赋值给 f
,如果结果是 Err(E)
,则返回该错误,所以 ?
特别适合用来传播错误。
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
// 打开文件,f是`Result<文件句柄,io::Error>`
let f = File::open("hello.txt");
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};
// 创建动态字符串s
let mut s = String::new();
// 从f文件句柄读取数据并写入s中
match f.read_to_string(&mut s) {
// 读取成功,返回Ok封装的字符串
Ok(_) => Ok(s),
// 将错误向上传播
Err(e) => Err(e),
}
}
上面的可以用? 替代。
#![allow(unused)]
fn main() {
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
}
12.5.1 try!¶
在当前版本中,我们要尽量避免使用 try!。
在 ?
横空出世之前 ( Rust 1.13 ),Rust 开发者还可以使用 try!
来处理错误,该宏的大致定义如下:
macro_rules! try {
($e:expr) => (match $e {
Ok(val) => val,
Err(err) => return Err(::std::convert::From::from(err)),
});
}
简单看一下与 ?
的对比:
// `?`
let x = function_with_error()?; // 若返回 Err, 则立刻返回;若返回 Ok(255),则将 x 的值设置为 255
// `try!()`
let x = try!(function_with_error());
可以看出 ?
的优势非常明显,何况 ?
还能做链式调用。
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
13 特殊语法¶
13.1 !Trait¶
!Trait
表示是不具有特征约束,和 Trait
用法一样。
以标准库 Send 特征为例,*const T
和 *mut T
被定义为 !Send
,即不具有 Send
特征约束的;而具有 T: Sync
特征的 &T
被定义为具有 Send
特征约束的。
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "Send")]
#[rustc_on_unimplemented(
message = "`{Self}` cannot be sent between threads safely",
label = "`{Self}` cannot be sent between threads safely"
)]
pub unsafe auto trait Send {
// empty.
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !Send for *const T {}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !Send for *mut T {}
// 大多数实例会自动出现,但需要此实例将 `T: Sync` 与 `&T: Send` 链接起来 (并且它还删除了不合理的默认实例 `T Send` -> `&T: Send` 否则会存在)。
//
//
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: Sync + ?Sized> Send for &T {}
13.2 *T
¶
Rust中在声明引用和指针类型和C++中很像,都是`&`表示引用,`*`表示指针,但不同的是rust中`&*`都是在类型前面`&T`、`*T`,而C++中是在类型后面`T&`、`T*`。
*T
是一个指针类型的写法,其中 T
是指针所指向的类型。这种写法通常被称为解引用操作符。
fn main() {
let x: i32 = 42;
let ptr: *const i32 = &x;
// 使用 *ptr 解引用指针,得到指针指向的值 (注意:访问指针是不安全的)
unsafe {
let value: i32 = *ptr;
println!("The value is: {}", value);
}
}
上面的 &x
表示取 x 的地址,不是指引用 x,这点和 C++ 中一样。*ptr
同理表示的是指针解引用,同样和 C++ 中一样。访问指针是不安全的,因此需要 unsafe
块。