Rust快速入门

Rust简介

Rust是一种编译语言,与CGo一样编译后生成的是二进制文件。Rust专注于安全速度并发。并且不需要垃圾回收器,这也让Rust在某些情况下比其他语言更好用。比如与其他语言混合编写嵌入其中、编写底层软件,驱动或操作系统。Rust在编译期间会将语法错误和不安全因素最大程度地找出来,从而避免在程序运行时出现问题。Rust语言可以胜任三个软件层次的开发。并且在今年5月15号发布了第一个1.0.0正式版本。

目录

Variable Bindings(变量绑定)

定义变量绑定使用let语句。

注意:在Rust中处于安全考虑,定义的变量默认都是不可修改的,如果需要定义可修改的变量需要在变量前加上mut关键字。

Rust是静态类型语言(statically typed language),在定义变量时若不指定变量类型,Rust会自动进行类型推导(type inference)。

1
2
3
4
fn main() {
let x = 5; //类型推导,定义的x类型为i32值为5
let mut x: i32 = 10; //定义一个类型为i32,值为10,可修改的变量绑定
}

const and static

定义常量

1
const N: i32 = 5;

定义全局(静态)变量绑定

1
2
3
4
5
6
7
static N: i32 = 5;
static mut N: i32 = 5;
//因为是全局变量,修改时需要在unsafe内
unsafe {
N += 1;
println!("N: {}", N);
}

Functions(函数)

Rust使用fn关键字来声明函数。

1
2
3
4
5
6
7
8
9
//定义一个无参数的函数
fn foo() {
println!("Hello Rust");
}

//定义一个有2个参数的函数
fn print_sum(x: i32, y: i32) {
println!("sum is: {}", x + y);
}

注意:函数的参数必须指定类型。

函数返回值,在函数声明中使用->指定返回的值的类型。函数的最后一行指定了要返回的值,而且这一行不能以分号;结尾,否则报错。当然你也可以使用return关键字来返回一个值。但如果需要返回的值在函数最后一行,通常不使用return

1
2
3
4
//返回一个32位整数类型
fn add_one(x: i32) -> i32 {
x + 1 //return x + 1;
}

Rust是基于表达式的语言,在Rust中只有两种语句(statements),声明语句(declaration statements) 和 表达式语句(expression statements),表达式语句的作用就是使表达式不返回值。其他的全都是表达式。主要区别为表达式返回值,语句不返回值。赋值表达式的返回值是空的元组()

若函数的返回值是!,称为发散函数(Diverging functions),代表该函数不会返回。如

1
2
3
fn diverges() -> ! {
panic!("This function never returns!");
}

panic!()宏(以!结尾的都是宏,类似的还有println!())的作用是使当前线程崩溃退出,并输出信息。因为运行该函数的线程崩溃退出了,所以该函数不会返回。(我暂时不知道这类发散函数有什么用-_-)

基本类型(Primitive Types)

  • Booleans
  • char Unicode 4字节
  • Numeric i8 i16 i32 i64 u8 u16 u32 u64 isize usize f32 f64
  • Arrays 数组的类型为[T; N]T为类型,N为数组长度
  • Slices Slices是对另一个数据结构的引用,目的是安全高效地对变量中的部分数据进行引用,类型为&[T]
  • str 字符串,经常以引用方式&str来使用
  • Tuples 元组与数组相似,但元素可以是不同的类型
  • Functions 函数指针类型
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
26
27
28
29
30
// Booleans
let x = true;
let y: bool = false;

// char
let x = 'x';
let two_hearts = '💕';

// Numeric
let x = 42; // x 类型为 i32
let y = 1.0; // y 类型为 f64

// Arrays
let mut m = [1, 2, 3]; // m: [i32; 3]
let a = [0; 20]; // a: [i32; 20] 初始化数组的值为0

// Slices
let a = [0, 1, 2, 3, 4];
let middle = &a[1..4]; // middle中只包含a数组中的[1,2,3]
let complete = &a[..]; // complete中包含a数组中的所有元素

// Tuples
let x = (1, "hello");
let x: (i32, &str) = (1, "hello");
let y = x.0;// 访问元组x的第一个元素

// Functions
fn foo(x: i32) -> i32 { x }
let x: fn(i32) -> i32 = foo;
// x 为foo函数的指针,函数参数为i32类型,返回类型为i32

Comments(注释)

Rust有两种注释,行注释(line comments)和文档注释(doc comments)。行注释跟C语言一样使用//,把当前行注释掉。文档注释使用///,而且支持Markdown语法。你可以使用rustdoc将文档注释生成为HTML文档。

1
2
3
4
5
6
7
8
9
10
11
12
///Adds one to the number given.
///
///# Examples
///
///```
///let five = 5;
///
///assert_eq!(6, add_one(5));
///```
fn add_one(x: i32) -> i32 {
x + 1
}

Control flow statement(控制语句)

if

1
2
3
4
5
6
7
8
9
let x = 5;

if x == 5 {
println!("x is five!");
} else if x == 6 {
println!("x is six!");
} else {
println!("x is not five or six :(");
}

for

1
2
3
4
5
6
7
for x in 0..10 {
println!("{}", x); // x: i32
}
//可以抽象为
for var in expression {
code
}

Python的for语句有点像,expression是一个迭代器(iterator)即一个可以进行遍历的对象,比如数组。

while

1
2
3
4
5
6
7
8
9
10
11
12
while x > 1 {
doSomething();
if done == true {
break;
} else {
continue;
}
}
loop {
doSomething();
//等价于 while true需要break跳出循环
}

Ownership

Ownership类似于作用域,在Rust程序中变量一旦离开作用域就会被释放。在变量绑定中,没有实现Copy特性的类型(如Vec)在进行赋值时,传递的是内存地址,而Rust出于安全考虑当两个变量绑定到同一值上时,Rust
会将原来的绑定删除,在函数传参时同样如此。而实现了Copy的类型(如基本类型i32)在进行赋值时传递的就是值。

1
2
3
4
5
6
7
let v = vec![1, 2, 3];
let v2 = v;
println!(v)//在这里就会报错,因为v的值([1, 2, 3]的地址)已经传给v2了

let v = 1;
let v2 = v;
println!("v is: {}", v);//在这里就不会报错,因为v是i32类型,传递的是值。

Borrowing

如果一个变量作为参数传入了函数中,那么该变量的作用域就变了,变为在函数内,所以在函数外该变量就不能使用了。在Rust中有Borrowing的概念,通过给函数传入变量的引用来达到"借用"的目的,使得变量在函数外还能继续使用(Borrow Ownership)。引用与变量绑定一样,默认是不可修改的。引用类似C语言的指针,因为在使用时得加上*符号。

1
2
3
4
5
6
7
8
9
//在类型前加上&符号表示该类型的引用
//在变量前加上&符号辨识变量的引用
fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
42
}
let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];
let answer = foo(&v1, &v2);
// v1 和 v2 还能继续使用

若要创建可修改的引用,需要使用&mut,而且被引用的变量也必须是可修改的。修改变量的引用,变量也会被修改。

1
2
3
4
5
6
7
let mut x = 5;
{
let y = &mut x;
*y += 1;
}
println!("{}", x);
//输出6

一个变量可以有多个引用(不可修改的引用),但同一时间(前一个可修改引用未被释放)只能有一个可修改引用。而且当作用域(scope)中存在变量的可修改引用(&mut T)时,无法创建该变量的引用。

1
2
3
4
5
6
7
8
let mut x = 5;

{
let y = &mut x; // -+ &mut borrow starts here
*y += 1; // |
} // -+ ... and ends here

println!("{}", x); // <- try to borrow x here

上面这个例子中x的可修改引用y在大括号中创建,并在大括号外释放,也就是限定了y变量的作用域。如果去掉那两个大括号最后的println!("{}", x)会报错。

Rust这么做主要是为了避免数据竞争(data race)当两个指针指向同一个内存地址时,会出现至少一个访问会等待,导致操作无法同步。

变量的定义必须在引用之前。

1
2
3
4
5
6
7
8
9
let y: &i32;
let x = 5;
y = &x;

println!("{}", y);//此处会发生错误,因为x的定义是在y被定义之后。
//正确的写法是
let x = 5;
let y: &i32;
y = &x;

Lifetimes

寿命(Lifetimes)指变量绑定在作用域内的范围。例如下面的例子中,变量的寿命可以显示或隐式定义。

1
2
3
4
5
6
7
// 隐式定义
fn foo(x: &i32, y: &mut i32) {
}

// 显示定义
fn bar<'a, 'b>(x: &'a i32, y: &'b mut i32) {//定义可修改引用的寿命&'b mut i32
}

定义变量的寿命主要是为了防止某个被引用的资源释放后,引用出错。(类似C中的野指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Foo<'a> {
x: &'a i32,
}

fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope

在上面的例子中struct Foo中的x的寿命就是y,因为x被赋值为y。而y的作用域仅在两个大括号内,所以当y被释放后,f也会被释放。

有一个特殊的变量寿命'static。也就是静态域,类似C++类中的静态变量。寿命为'static的变量绑定会在整个程序中都存在。

1
2
static FOO: i32 = 5;
let x: &'static i32 = &FOO;

函数参数的寿命

  • 函数的每个参数若省略定义寿命名则每个参数都有一个独立的寿命名。(只有引用类型的参数才需要寿命名)
1
2
fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command // 省略
fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // 显示定义
  • 如果只有一个输入寿命(无论是否省略),该寿命应用于函数的所有返回值。
1
2
fn new(buf: &mut [u8]) -> BufWriter; 
fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>
  • 如果有多个输入寿命,其中一个为&self&mut selfself的寿命将应用于所有省略了寿命的返回值。
1
2
fn get_mut(&mut self) -> &mut T; 
fn get_mut<'a>(&'a mut self) -> &'a mut T;

以上这3个Rust中的概念确实比较难懂,我是根据自己的理解写的,若有不同观点请看官方原文。

Structs

与C语言的结构体类似,将某些数据类型组合在一起,形成新的数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Point {  //名称第一个字母大写,采用驼峰命名法
x: i32, //不能写成mut x: i32,
y: i32,
}

fn main() {
let origin = Point { x: 0, y: 0 }; //定义一个Point类型的变量绑定,并赋值

println!("The origin is at ({}, {})", origin.x, origin.y);//struct变量访问
}

let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point };
//新的ponit y为1,x和z使用原来的point的值

Tuple structs

定义一个类似于tuple的结构。

1
2
3
4
5
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
//以下两个变量不相等
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Unit-like structs

可以定义一个无成员的结构

1
struct Electron;

Enums

Rust的枚举类型,类型为Message的变量绑定可以是Message的其中之一

1
2
3
4
5
6
7
8
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}

let x: Message = Message::Move { x: 3, y: 4 };

Match

match表达式类似于C语言中的switch

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
26
27
28
29
30
31
let x = 5;

match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
4 => println!("four"),
5 => println!("five"),
_ => println!("something else"),//当所有值都不匹配时
}

//x可以为`enum`类型
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}

fn quit() { /* ... */ }
fn change_color(r: i32, g: i32, b: i32) { /* ... */ }
fn move_cursor(x: i32, y: i32) { /* ... */ }

fn process_message(msg: Message) {
match msg {
Message::Quit => quit(),
Message::ChangeColor(r, g, b) => change_color(r, g, b),
Message::Move { x: x, y: y } => move_cursor(x, y),
Message::Write(s) => println!("{}", s),
};
}

Patterns

模式,match中x所匹配的就是模式

1
2
3
4
5
6
7
8
9
let x = 1;

match x {
1 | 2 => println!("one"), //匹配1或2
3 ... 7 => println!("two"),//匹配3, 4, 5, 6, 7
'a' ... 'j' => println!("three"),//匹配字母a到j
e @ 8 ... 10 => println!("got a range element {}", e),//若x为10,e所绑定的值就为10
_ => println!("anything"),
}

匹配数据结构的一部分

1
2
3
4
5
6
7
8
9
10
11
#[derive(Debug)]
struct Person {
name: Option<String>,
}

let name = "Steve".to_string();
let mut x: Option<Person> = Some(Person { name: Some(name) });
match x {
Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a),
_ => {}
}

匹配有变量的枚举类型,使用..来忽略掉参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum OptionalInt {
Value(i32),
Missing,
}

let x = OptionalInt::Value(5);
let mut y = 5;

match x {
OptionalInt::Value(i) if i > 5 => println!("Got an int bigger than five!"),//添加if作为判断
OptionalInt::Value(..) => println!("Got an int!"),
OptionalInt::Missing => println!("No such luck."),
ref y => println!("Got a reference to {}", y),//获取引用
ref mut mr => println!("Got a mutable reference to {}", mr),
}//最后输出Got an int!

匹配struct类型

1
2
3
4
5
6
7
8
9
10
11
struct Point {
x: i32,
y: i32,
}

let origin = Point { x: 0, y: 0 };

match origin {
Point { x: x, y: y } => println!("({},{})", x, y),
Point { x: x, .. } => println!("x is {}", x),
}

以上列出的匹配可以任意组合在一起。

Method Syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Circle {
x: f64,
y: f64,
radius: f64,
}

impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}

fn main() {
let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
println!("{}", c.area());
}

先定义一个structCircle,再用implCircle中添加一个方法area,每个方法都会有一个特殊的参数,可以是self&self&mut self其中之一。 传值方式与Functions一节相同。在上面的代码中self指代的就是c这个变量(类似于其他语言中的this),所以在这里我们使用的是引用,而且一般情况下也都是使用引用。

Chaining method calls(链式调用)

通过返回self来达到链式调用的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Circle {
x: f64,
y: f64,
radius: f64,
}

impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}

fn grow(&mut self, increment: f64) -> &Circle {
self.radius += increment;
return self;
}
}

fn main() {
let mut c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
println!("{}", c.area());

let d = c.grow(2.0).area();
println!("{}", d);
}

官方的代码是返回一个新的Circle。此处我做了下修改以更符合返回self的一般情况。

Associated functions

联合函数不需要self参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Circle {
x: f64,
y: f64,
radius: f64,
}

impl Circle {
//new方法返回一个Circle
fn new(x: f64, y: f64, radius: f64) -> Circle {
Circle {
x: x,
y: y,
radius: radius,
}
}
}

fn main() {
let c = Circle::new(0.0, 0.0, 2.0);//联合函数的调用方法Struct::function()
//类似于其他语言中的静态方法
}

Builder Pattern

为了使用户只能修改struct中特定的属性,需要使用另一个struct来作限制,如CircleCircleBuilder

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
struct Circle {
x: f64,
y: f64,
radius: f64,
}

impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}

struct CircleBuilder {
x: f64,
y: f64,
radius: f64,
}

impl CircleBuilder {
fn new() -> CircleBuilder {
CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
}

fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
self.x = coordinate;
self
}

fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
self.y = coordinate;
self
}

fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
self.radius = radius;
self
}

fn finalize(&self) -> Circle {
Circle { x: self.x, y: self.y, radius: self.radius }
}
}

fn main() {
let c = CircleBuilder::new()
.x(1.0)
.y(2.0)
.radius(2.0)
.finalize();

println!("area: {}", c.area());
println!("x: {}", c.x);
println!("y: {}", c.y);
}

通过使用CircleBuilder来创建Circle,就可以对Circle的创建和修改做出约束。

Vectors (向量)

向量(Vec<T>)是动态可增长的数组,存储在堆上。使用vec!宏创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let v = vec![1, 2, 3, 4, 5]; // v: Vec<i32>
let v = vec![0; 10]; // 10 个 0
println!("The third element of v is {}", v[2]);//下标从0开始
//遍历向量
for i in &v {
println!("A reference to {}", i);
}

for i in &mut v {
println!("A mutable reference to {}", i);
}

for i in v {
println!("Take ownership of the vector and its element {}", i);
}

Strings

Rust有两种字符串类型&str(&'static str)和String,都是UTF-8编码(一个字符占4字节)

&str类型的字符串如

1
let string = "Hello there."; // string: &'static str

存在于静态域,整个程序都可以访问,固定长度,无法被修改。

String是在堆上创建的字符串,可加长,通常使用to_string&str格式化得到。

1
2
3
4
5
let mut s = "Hello".to_string(); // mut s: String 使用了to_string()方法才可以修改s
println!("{}", s);

s.push_str(", world.");
println!("{}", s);

可以使用&String强制格式化为&str

1
2
3
4
5
6
7
8
fn takes_slice(slice: &str) {
println!("Got: {}", slice);
}

fn main() {
let s = "Hello".to_string();
takes_slice(&s);
}

注意: String可以轻易地变成&str,但&str格式化为String需要分配内存(因为是在堆上创建),所以必要情况下才这么做。

Indexing

无法通过s[0]来访问某个字符,因为字符是UTF-8编码,但可以这样做

1
2
let hachiko = "忠犬ハチ公";
let dog = hachiko.chars().nth(1); // 类似于 hachiko[1]

注意: chars()操作需要遍历整个字符串

Concatenation

如果你有一个String类型的字符串,可以将&str类型的字符串连接到末尾。

1
2
3
let hello = "Hello ".to_string();
let world = "world!";
let hello_world = hello + world;

如果是两个String类型的字符串,连接时第二个需要转换为&str类型

1
2
3
let hello = "Hello ".to_string();
let world = "world!".to_string();
let hello_world = hello + &world;

Generics(泛型)

1
2
3
4
5
6
enum Option<T> {//定义中的T可以换成其他大写字母
Some(T),
None,
}

let x: Option<i32> = Some(5);

<T>说明该类型是泛型。

Generic functions(泛型函数)

1
2
3
4
5
6
7
fn takes_anything<T>(x: T) {
// do something with x
}
fn takes_two_of_the_same_things<T>(x: T, y: T) {
}
fn takes_two_things<T, U>(x: T, y: U) {
}

Generic structs(泛型结构)

1
2
3
4
5
6
7
struct Point<T> {
x: T,
y: T,
}

let int_origin = Point { x: 0, y: 0 };
let float_origin = Point { x: 0.0, y: 0.0 };

Traits

Traits的作用类似于其他语言的接口,比如Java的Interface类型。在其中定义的函数只写声明部分。用于约束泛型中必须定义了哪些函数。比如:

1
2
3
fn print_area<T>(shape: T) {
println!("This shape has an area of {}", shape.area());
}

编译的时候会发生错误,因为泛型T无法保证是否定义了area()函数。所以需要使用Traits

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
trait HasArea {
fn area(&self) -> f64;
}

struct Circle {
x: f64,
y: f64,
radius: f64,
}

impl HasArea for Circle { //语法impl Trait for Item
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}

struct Square {
x: f64,
y: f64,
side: f64,
}

impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}

fn print_area<T: HasArea>(shape: T) {//T: HasArea 意思是所有实现了HasArea trait的类型
println!("This shape has an area of {}", shape.area());
}

fn main() {
let c = Circle {
x: 0.0f64,
y: 0.0f64,
radius: 1.0f64,
};

let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 1.0f64,
};

print_area(c);
print_area(s);
}

除了自定义的类型外,也可以为基本类型或者其他已有类型实现自己的Trait

1
2
3
4
5
6
7
8
9
10
11
12
13
trait HasArea {
fn area(&self) -> f64;
}

impl HasArea for i32 {
fn area(&self) -> f64 {
println!("this is silly");

*self as f64
}
}

5.area();

注意: trait同样有作用域,超出作用域就无法使用。

实现多个trait使用+符号

1
2
3
4
5
6
use std::fmt::Debug;

fn foo<T: Clone + Debug>(x: T) {
x.clone();
println!("{:?}", x);
}

where从句

为了避免在多trait在声明参数时过长,使用where从句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::fmt::Debug;

fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
//上面的写法可以写为
fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}

fn main() {
foo("Hello", "world");
bar("Hello", "workd");
}

Default methods (默认方法)

trait中也可以包含默认的方法(可以是多个),即在定义trait时就被实现的函数,所以在实现trait时就不需要实现已经被实现的函数,但仍可重写该函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
trait Foo {
fn bar(&self);
fn baz(&self) { println!("We called baz."); }
}

struct UseDefault;

impl Foo for UseDefault {//只需要实现bar(),因为baz已经在定义trait Foo时被实现了
fn bar(&self) { println!("We called bar."); }
}

struct OverrideDefault;

impl Foo for OverrideDefault {
fn bar(&self) { println!("We called bar."); }

fn baz(&self) { println!("Override baz!"); }
}

Inheritance (继承)

当实现Foo时也需要实现FooBar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trait Foo {
fn foo(&self);
}

trait FooBar : Foo { //使用 ":"符号来实现trait的继承
fn foobar(&self);
}

struct Baz;

impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}

impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}

Drop

Droptrait中的一个特殊函数,类似于析构函数,当变量绑定离开作用域后Drop方法就会被调用,常用来释放不再使用的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct HasDrop;

impl Drop for HasDrop {
fn drop(&mut self) {
println!("Dropping!");
}
}

fn main() {
let x = HasDrop;

// do stuff

} // x 在这里离开main的作用域

注意: 同一作用域中的变量离开作用域后被释放的顺序与被定义的顺序相反。

if let

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//将
match option {
Some(x) => { foo(x) },
None => {},
}
//写成
if let Some(x) = option {
foo(x);
} else {
bar();
}

//使用while let 将
loop {
match option {
Some(x) => println!("{}", x),
_ => break,
}
}
//写成
while let Some(x) = option {
println!("{}", x);
}

Trait Objects

Dynamic dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trait Foo {
fn method(&self) -> String;
}

impl Foo for u8 {
fn method(&self) -> String { format!("u8: {}", *self) }
}

impl Foo for String {
fn method(&self) -> String { format!("string: {}", *self) }
}

fn do_something(x: &Foo) {//&Foo可以作为一种类型来使用,x也就是Trait Objects
x.method();
}

fn main() {
let x = 5u8;
do_something(&x as &Foo);
}

Closures (闭包)

1
2
3
4
5
6
7
8
9
10
11
12
let plus_one = |x: i32| x + 1;

assert_eq!(2, plus_one(1));//断言,调试用
//闭包也可以写成
let plus_two = |x| {
let mut result: i32 = x;

result += 1;
result += 1;

result
};

创建了一个绑定plus_one,并指派给一个闭包。在|之间的是闭包的参数,后面跟着闭包的主体。闭包可以不指定参数和返回值的类型。

将闭包作为函数传递

1
2
3
4
5
6
7
8
9
fn call_with_one<F>(some_closure: F) -> i32
where F : Fn(i32) -> i32 {

some_closure(1)
}

let answer = call_with_one(|x| x + 2);

assert_eq!(3, answer);

有点像函数式编程的匿名函数。既然能传入,当然也能当做返回值。

Universal Function Call Syntax

当函数有相同名字时

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
trait Foo {
fn f(&self);
}

trait Bar {
fn f(&self);
}

struct Baz;

impl Foo for Baz {
fn f(&self) { println!("Baz’s impl of Foo"); }
}

impl Bar for Baz {
fn f(&self) { println!("Baz’s impl of Bar"); }
}

let b = Baz;
//b.f()会发生错误
Foo::f(&b);
Bar::f(&b);

<Type as Trait>::method(args);

trait Foo {
fn clone(&self);
}

#[derive(Clone)]
struct Bar;

impl Foo for Bar {
fn clone(&self) {
println!("Making a clone of Bar");

<Bar as Clone>::clone(self);
}
}
//调用Clone trait中的 clone()

Attributes

属性的声明,类似于#[]都是属性,属性用于提供某些辅助作用。

1
2
3
4
5
6
#[foo]//该属性作用于它的下一条语句
struct Foo;

mod bar {
#![bar]//该属性作用于包含它的语句,在这里就是mod声明
}

type Aliases (类型别名)

类似于C语言的typedef

1
2
3
4
type Name = String;
let x: Name = "Hello".to_string();

type Result<T> = result::Result<T, ConcreteError>;

Casting Between Types (类型转换)

一般的类型转换使用as,强制类型转换使用transmute

1
2
3
4
5
6
7
8
9
10
11
12
let x: i32 = 5;
let y = x as i64;
let a = [0u8, 0u8, 0u8, 0u8];
let b = a as u32; // 使用as转换将出错

use std::mem;

unsafe {
let a = [0u8, 0u8, 0u8, 0u8];

let b = mem::transmute::<[u8; 4], u32>(a);
}

Operators and Overloading (操作符和重载)

使用Addtrait来对+进行重载

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
26
27
28
29
30
31
32
33
34
use std::ops::Add;

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}

impl Add for Point {
type Output = Point;

fn add(self, other: Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}

fn main() {
let p1 = Point { x: 1, y: 0 };
let p2 = Point { x: 2, y: 3 };

let p3 = p1 + p2;

println!("{:?}", p3);
}
//还可以为不同类型之间的数据重载加法运算
impl Add<i32> for Point {
type Output = f64;

fn add(self, rhs: i32) -> f64 {
// add an i32 to a Point and get an f64
}
}
let p: Point = // ...
let x: f64 = p + 2i32;

参考页面