Rust学习篇,介绍Rust的基础知识。
基本概念
变量
变量默认是不可改变的(immutable)
可变:
不允许对常量使用
mut
。常量不光默认不能变,它总是不能变。声明常量使用
const
关键字而不是 let
,并且 必须
注明值的类型。
1
| const MAX_POINTS: u32 = 100_000;
|
新变量会 隐藏 之前的变量
1 2 3 4 5 6
| fn main() { let x = 5; let x = x + 1; let x = x * 2; println!("The value of x is: {}", x); }
|
变量类型
标量类型
标量(scalar)类型代表一个单独的值。Rust
有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过它们。让我们深入了解它们在
Rust 中是如何工作的。
整型
整数 是一个没有小数部分的数字。我们在第二章使用过
u32
整数类型。该类型声明表明,它关联的值应该是一个占据 32
比特位的无符号整数(有符号整数类型以 i
开头而不是
u
)。
Rust 中的整型
长度 |
有符号 |
无符号 |
8-bit |
i8 |
u8 |
16-bit |
i16 |
u16 |
32-bit |
i32 |
u32 |
64-bit |
i64 |
u64 |
arch |
isize |
usize |
Rust 中的整型字面值
数字字面值 |
例子 |
Decimal |
98_222 |
Hex |
0xff |
Octal |
0o77 |
Binary |
0b1111_0000 |
Byte (u8 only) |
b'A' |
浮点类型
Rust 也有两个原生的 浮点数(floating-point
numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是
f32
和 f64
。默认类型是 f64
。
bool类型
正如其他大部分编程语言一样,Rust
中的布尔类型有两个可能的值:true
和
false
。Rust 中的布尔类型使用 bool
表示。
字符类型
目前为止只使用到了数字,不过 Rust 也支持字母。Rust 的
char
类型是语言中最原生的字母类型,如下代码展示了如何使用它。(注意
char
由单引号指定,不同于字符串使用双引号。)
1 2 3 4 5
| fn main() { let c = 'z'; let z = 'ℤ'; let heart_eyed_cat = '😻'; }
|
复合类型
元组
1 2 3
| fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); }
|
tup
变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配(pattern
matching)来解构(destructure)元组值,像这样:
1 2 3 4 5
| fn main() { let tup = (500, 6.4, 1); let (x, y, z) = tup; println!("The value of y is: {}", y); }
|
访问元组元素使用.
1 2 3 4 5 6 7 8 9
| fn main() { let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2; }
|
数组
1 2 3
| fn main() { let a = [1, 2, 3, 4, 5]; }
|
与元组不同,数组中的每个元素的类型必须相同。Rust
中的数组与一些其他语言中的数组不同,因为 Rust
中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。
1
| let a: [i32; 5] = [1, 2, 3, 4, 5];
|
[type:number]用于表示数组的类型
访问方式同C,用中括号
String
类型
1
| let s = String::from("hello");
|
可以 修改此类字符串 :
1 2 3 4 5 6
| let mut s = String::from("hello");
s.push_str(", world!");
println!("{}", s);
|
函数
fn
关键字,它用来声明新函数
例子:
1 2 3 4 5 6 7
| fn main() { another_function(5); }
fn another_function(x: i32) { println!("The value of x is: {}", x); }
|
语句和表达式
1 2 3 4 5 6 7 8 9 10
| fn main() { let x = 5;
let y = { let x = 3; x + 1 };
println!("The value of y is: {}", y); }
|
分号视作是语句的结束,如果没有分号结尾,我们默认认为最后的语句用于作为返回值。
函数如果有返回值的话我们需要使用->
用于表示返回值类型。
1 2 3 4 5 6 7 8 9
| fn five() -> i32 { 5 }
fn main() { let x = five();
println!("The value of x is: {}", x); }
|
控制流
分支
1 2 3 4 5 6 7 8 9
| fn main() { let number = 3;
if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
|
1 2 3 4 5 6 7 8 9 10
| fn main() { let condition = true; let number = if condition { 5 } else { 6 };
println!("The value of number is: {}", number); }
|
需要注意的是如果使用下面的赋值分支,我们每个分支内部的返回值类型都需要设置为同一类型。
循环
loop
1 2 3 4 5
| fn main() { loop { println!("again!"); } }
|
loop用于执行死循环,可以用break跳出循环
1 2 3 4 5 6 7 8 9 10 11 12 13
| fn main() { let mut counter = 0;
let result = loop { counter += 1;
if counter == 10 { break counter * 2; } };
assert_eq!(result, 20); }
|
break的用法不同与我们的C,有返回值
while
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let mut number = 3;
while number != 0 { println!("{}!", number);
number = number - 1; }
println!("LIFTOFF!!!"); }
|
for
1 2 3 4 5 6
| fn main() { let a = [10, 20, 30, 40, 50];
for element in a.iter() { println!("the value is: {}", element); }
|
1 2 3 4 5 6
| fn main() { for number in (1..4).rev() { println!("{}!", number); } println!("LIFTOFF!!!"); }
|
所有权
作为Rust独有的新概念
- Rust 中的每一个值都有一个被称为其
所有者(owner)的变量。
- 值有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
作用域(scope)
这个变量从声明的点开始直到当前 作用域
结束时都是有效的。
1 2 3 4 5
| { let s = "hello";
}
|
变量与数据交互
移动
1 2
| let s1 = String::from("hello"); let s2 = s1;
|
此时s1失效,不再有用
我们称作s1被移到s2中
克隆
1 2 3 4
| let s1 = String::from("hello"); let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
|
此时s1和s2都是有效的
拷贝
1 2 3 4
| let x = 5; let y = x;
println!("x = {}, y = {}", x, y);
|
没有调用 clone
,不过 x
依然有效且没有被移动到 y
中
Rust 有一个叫做 Copy
trait
的特殊注解,可以用在类似整型这样的存储在栈上的类型上(第十章详细讲解
trait)。如果一个类型拥有 Copy
trait,一个旧的变量在将其赋值给其他变量后仍然可用。
如下是一些 Copy
的类型:
- 所有整数类型,比如
u32
。
- 布尔类型,
bool
,它的值是 true
和
false
。
- 所有浮点数类型,比如
f64
。
- 字符类型,
char
。
- 元组,当且仅当其包含的类型也都是
Copy
的时候。比如,(i32, i32)
是 Copy
的,但
(i32, String)
就不是。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| fn main() { let s = String::from("hello");
takes_ownership(s);
let x = 5;
makes_copy(x);
}
fn takes_ownership(some_string: String) { println!("{}", some_string); }
fn makes_copy(some_integer: i32) { println!("{}", some_integer); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| fn main() { let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); }
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string }
fn takes_and_gives_back(a_string: String) -> String {
a_string }
|
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过
drop
被清理掉,除非数据被移动为另一个变量所有。
在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,我们也可能想返回函数体中产生的一些数据。我们可以使用元组来返回多个值。
1 2 3 4 5 6 7 8 9 10 11 12 13
| fn main() { let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len); }
fn calculate_length(s: String) -> (String, usize) { let length = s.len();
(s, length) }
|
引用和借用
一般引用
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len); }
fn calculate_length(s: &String) -> usize { s.len() }
|
注意我们传递 &s1
给
calculate_length
,同时在函数定义中,我们获取
&String
而不是 String
。
这些 & 符号就是
引用,它们允许你使用值但不获取其所有权。
1 2 3 4
| fn calculate_length(s: &String) -> usize { s.len() }
|
我们将获取引用作为函数参数称为
借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们尝试修改借用的变量是不可能的。
可变引用
1 2 3 4 5 6 7 8 9
| fn main() { let mut s = String::from("hello");
change(&mut s); }
fn change(some_string: &mut String) { some_string.push_str(", world"); }
|
不过可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用。
这个限制的好处是 Rust
可以在编译时就避免数据竞争。数据竞争(data
race)类似于竞态条件,它可由这三个行为造成:
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能
同时 拥有.
1 2 3 4 5 6
| let mut s = String::from("hello");
let r1 = &mut s; let r2 = &mut s;
println!("{}, {}", r1, r2);
|
类似的规则也存在于同时使用可变与不可变引用中。我们不能同时有可变和不可变的引用。
1 2 3 4 5 6 7
| let mut s = String::from("hello");
let r1 = &s; let r2 = &s; let r3 = &mut s;
println!("{}, {}, and {}", r1, r2, r3);
|
悬垂引用
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个
悬垂指针(dangling
pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在
Rust
中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
1 2 3 4 5 6 7 8 9
| fn main() { let reference_to_nothing = dangle(); }
fn dangle() -> &String { let s = String::from("hello");
&s }
|
错误信息引用了一个我们还未介绍的功能:生命周期(lifetimes)。
因为 s
是在 dangle
函数内创建的,当
dangle
的代码执行完毕后,s
将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的
String
,这可不对!Rust 不会允许我们这么做。
这里的解决方法是直接返回 String
:
1 2 3 4 5
| fn no_dangle() -> String { let s = String::from("hello");
s }
|
引用的规则
让我们概括一下之前对引用的讨论:
- 在任意给定时间,要么
只能有一个可变引用,要么 只能有多个不可变引用。
- 引用必须总是有效。
Slice类型
字符串 slice(string slice)是
String
中一部分值的引用,它看起来像这样:
1 2 3 4 5
| let s = String::from("hello world");
let hello = &s[0..5]; let world = &s[6..11];
|
这类似于引用整个 String
不过带有额外的
[0..5]
部分。它不是对整个 String
的引用,而是对部分 String
的引用。start..end
语法代表一个以 start
开头并一直持续到但不包含
end
的 range。如果需要包含 end
,可以使用
..=
而不是 ..
:
1 2 3 4 5
| let s = String::from("hello world");
let hello = &s[0..=4]; let world = &s[6..=10];
|
一个真正获取 部分
字符串的办法。不过,我们可以返回单词结尾的索引。
如果想要从第一个索引(0)开始,可以不写两个点号之前的值
如果 slice 包含 String
的最后一个字节,也可以舍弃尾部的数字
在记住所有这些知识后,让我们重写 first_word
来返回一个
slice。“字符串 slice” 的类型声明写作 &str
:
文件名: src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12
| fn first_word(s: &String) -> &str { let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } }
&s[..] }
|
1 2 3 4 5 6 7 8 9
| fn main() { let mut s = String::from("hello world");
let word = first_word(&s);
s.clear();
println!("the first word is: {}", word); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| fn main() { let my_string = String::from("hello world");
let word = first_word(&my_string[..]);
let my_string_literal = "hello world";
let word = first_word(&my_string_literal[..]);
let word = first_word(my_string_literal); }
|
1 2 3
| let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
|
这个 slice 的类型是 &[i32]
。它跟字符串 slice
的工作方式一样,通过存储第一个集合元素的引用和一个集合总长度。你可以对其他所有集合使用这类
slice。