HelloWorld
1
2
3
fn main () {
println ! ( "Hello, world!" );
}
! 表示这是一个宏(macro),而非一个函数(function)
1
2
rustc hello.rs
./hello
Installation
1
2
3
brew install rustup-init
export RUSTUP_DIST_SERVER = https://mirrors.tuna.tsinghua.edu.cn/rustup
rustup-init
1
2
rustc --version
cargo --version
编辑 $HOME/.cargo/config
1
2
3
4
5
[ source . crates-io ]
replace-with = 'tuna'
[ source . tuna ]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
Cargo
包管理类似pip npm 但还提供一些工作流
创建新项目:cargo new
编译:cargo build
运行:cargo run
更新项目依赖:cargo update
执行测试:cargo test
生成文档:cargo doc
静态检查:cargo check
1
2
3
4
5
$ tree
.
├── Cargo.toml
└── src
└── main.rs
Cargo.toml 是工程的描述文件,包含 Cargo 所需的所有元信息。
1
2
3
4
5
6
7
8
9
[ package ]
name = "helloworld"
version = "0.1.0"
authors = [ "iczc <[email protected] >" ]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[ dependencies ]
在Rust中,依赖的代码包被称为crates https://crates.io/
src 放置源代码, 约定:main.rs / lib.rs 是入口文件。--lib
可以生成库项目
运行 cargo run 或 cargo build,可执行文件将生成在 target/debug/ 目录,运行 cargo build –release,可执行文件将生成在 target/release/
Basic
变量
Rust 中变量默认是不可变的(immutable),使用 mut 标志为可变(mutable) 静态类型语言 支持类型推导
1
2
3
4
5
6
7
8
9
10
// 不可变
let c ;
let a = true ;
let b : bool = true ;
let ( x , y ) = ( 1 , 2 );
c = 12345 ;
// 可变
let mut z = 5 ;
z = 6 ;
不需要追踪一个不可变值如何和在哪可能会被改变
常量
常量用const声明,只能被设置为常量表达式,而不能是函数调用的结果,一般会被编译器内联优化而不分配内存空间。
1
const MAX_POINTS : u32 = 100_000 ;
函数
1
2
3
4
5
6
7
8
9
10
11
12
fn print_sum ( a : i8 , b : i8 ) { // 参数
println ! ( "sum is: {}" , a + b );
}
fn plus_one ( a : i32 ) -> i32 { // 有返回值
a + 1
// 等价于 return a + 1
}
fn plus_one ( a : i32 ) -> ( i32 , i32 ) { // 使用元组返回多值
( a , & a + 1 )
}
基本数据类型
布尔值(bool)
字符(char) // Unicode
有符号整型(i8, i16, i32, i64, i128)
无符号整型(u8, u16, u32, u64, u128)
浮点数(f32, f64)
数组(arrays),由相同类型元素构成,长度固定。
1
2
3
4
5
6
7
8
9
10
let a = [ 1 , 2 , 3 ]; // a[0] = 1, a[1] = 2, a[2] = 3
let mut b = [ 1 , 2 , 3 ];
let c : [ int ; 3 ] = [ 1 , 2 , 3 ]; // [类型; 数组长度]
let d : [ "my value" ; 3 ]; //["my value", "my value", "my value"];
let e : [ i32 ; 0 ] = []; // 空数组
println ! ( "{:?}" , a ); //[1, 2, 3]
动态数组可以用vector,会在堆上分配空间。
元组(tuples),由相同/不同类型元素构成,长度固定。
1
2
3
4
5
6
7
8
9
10
11
12
13
let a = ( 1 , 1.5 , true , 'a' , "Hello, world!" );
// a.0 = 1, a.1 = 1.5, a.2 = true, a.3 = 'a', a.4 = "Hello, world!"
let b : ( i32 , f64 ) = ( 1 , 1.5 );
let ( c , d ) = b ; // c = 1, d = 1.5 // 解构
let ( e , _ , _ , _ , f ) = a ; //e = 1, f = "Hello, world!", _ 作为占位符使用,表示忽略该位置的变量
let g = ( 0 ,); // 只包含一个元素的元组 类型注解是可选的
let h = ( b , ( 2 , 4 ), 5 ); //((1, 1.5), (2, 4), 5)
println ! ( "{:?}" , a ); //(1, 1.5, true, 'a', "Hello, world!")
使用:a.1
运算符
1
2
3
4
5
6
7
8
9
10
11
12
fn main () {
// 加法
let sum = 5 + 10 ;
// 减法
let difference = 95.5 - 4.3 ;
// 乘法
let product = 4 * 30 ;
// 除法
let quotient = 56.7 / 32.2 ;
// 取余
let remainder = 43 % 5 ;
}
比较运算符 == != < > <= >=
逻辑运算符 ! && ||
位运算符 & | ^ « »
赋值运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 不支持 i++ ++i i-- --i 自增运算符
let mut a = 2 ;
a += 5 ; //2 + 5 = 7
a -= 2 ; //7 - 2 = 5
a *= 5 ; //5 * 5 = 25
a /= 2 ; //25 / 2 = 12 not 12.5
a %= 5 ; //12 % 5 = 2
a &= 2 ; //10 && 10 -> 10 -> 2
a |= 5 ; //010 || 101 -> 111 -> 7
a ^= 2 ; //111 != 010 -> 101 -> 5
a <<= 1 ; //'101'+'0' -> 1010 -> 10
a >>= 2 ; //101̶0̶ -> 10 -> 2
1
2
let a = 15 ;
let b = ( a as f64 ) / 2.0 ; //7.5
控制流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main () {
let number = 6 ;
if number % 4 == 0 {
println ! ( "number is divisible by 4" );
} else if number % 3 == 0 {
println ! ( "number is divisible by 3" );
} else if number % 2 == 0 {
println ! ( "number is divisible by 2" );
} else {
println ! ( "number is not divisible by 4, 3, or 2" );
}
let x = 5 ;
let y = if x == 5 { 10 } else { 15 }; //Rust中if是表达式 返回值可以赋值给变量 类似三元运算符
}
1
2
3
4
5
6
7
8
9
let tshirt_width = 20 ;
let tshirt_size = match tshirt_width {
16 => "S" , // check 16
17 | 18 => "M" , // 多值匹配
19 ... 21 => "L" , // 范围匹配
22 => "XL" ,
_ => "Not Available" , // default
};
println ! ( "{}" , tshirt_size ); // L
1
2
3
4
5
fn main () {
loop {
println ! ( "again!" );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
let mut a = 1 ;
while a <= 10 {
println ! ( "Current value : {}" , a );
a += 1 ;
}
let a = [ 10 , 20 , 30 , 40 , 50 ];
for element in a . iter () {
println ! ( "the value is: {}" , element );
}
Guessing game
尝试
$ cargo new guessing_game
$ cd guessing_game
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std ::io ; // 使用标准库io
fn main () { // 定义函数
/* 一个小游戏 */
println ! ( "Guess the number!" );
println ! ( "Please input your guess." );
let mut guess = String ::new (); // 使用let创建变量 let foo = bar; new是静态方法
io ::stdin (). read_line ( & mut guess )
. expect ( "Failed to read line" ); // 从标准输入读并存到guess变量 read_line返回result枚举类型 后面我们详细说明
println ! ( "You guessed: {}" , guess );
}
引入外部crate
https://crates.io/crates/rand
1
2
[ dependencies ]
rand = "0.8.2"
cargo build
生成随机数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std ::io ;
use rand ::prelude ::* ;
fn main () {
println ! ( "Guess the number!" );
let secret_number = rand ::thread_rng (). gen_range ( 1 .. 100 );
println ! ( "The secret number is: {}" , secret_number );
println ! ( "Please input your guess." );
let mut guess = String ::new ();
io ::stdin (). read_line ( & mut guess )
. expect ( "Failed to read line" );
println ! ( "You guessed: {}" , guess );
}
比较结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std ::io ;
use std ::cmp ::Ordering ;
use rand ::Rng ;
fn main () {
// ---snip---
let guess : u32 = guess . trim (). parse ()
. expect ( "Please type a number!" ); // 字符串转整数
println ! ( "You guessed: {}" , guess );
match guess . cmp ( & secret_number ) {
Ordering ::Less => println ! ( "Too small!" ),
Ordering ::Greater => println ! ( "Too big!" ),
Ordering ::Equal => println ! ( "You win!" ),
}
}
Ordering是一个枚举,它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果。
Rust 允许用一个新值来隐藏guess 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用guess变量的名字,而不是被迫创建两个不同变量,诸如 guess_str 和 guess 之类。
使用循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// --snip--
loop {
println ! ( "Please input your guess." );
// --snip--
match guess . cmp ( & secret_number ) {
Ordering ::Less => println ! ( "Too small!" ),
Ordering ::Greater => println ! ( "Too big!" ),
Ordering ::Equal => {
println ! ( "You win!" );
break ;
}
}
}
处理无效输入
1
2
3
4
5
6
7
8
9
10
11
12
13
// --snip--
io ::stdin (). read_line ( & mut guess )
. expect ( "Failed to read line" );
let guess : u32 = match guess . trim (). parse () {
Ok ( num ) => num ,
Err ( _ ) => continue ,
};
println ! ( "You guessed: {}" , guess );
// --snip--
将 expect 调用换成 match 语句,这样不会遇到错误时就崩溃,parse 返回一个 Result 类型,而 Result 是一个拥有 Ok 或 Err 成员的枚举。这里使用的 match 表达式,和之前处理 cmp 方法返回 Ordering 时用的一样。
关于枚举与错误处理,我们后面详细讲到
最终版
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 ::io ;
use std ::cmp ::Ordering ;
use rand ::prelude ::* ;
fn main () {
println ! ( "Guess the number!" );
let secret_number = rand ::thread_rng (). gen_range ( 1 .. 100 );
loop {
println ! ( "Please input your guess." );
let mut guess = String ::new ();
io ::stdin (). read_line ( & mut guess )
. expect ( "Failed to read line" );
let guess : u32 = match guess . trim (). parse () {
Ok ( num ) => num ,
Err ( _ ) => continue ,
};
println ! ( "You guessed: {}" , guess );
match guess . cmp ( & secret_number ) {
Ordering ::Less => println ! ( "Too small!" ),
Ordering ::Greater => println ! ( "Too big!" ),
Ordering ::Equal => {
println ! ( "You win!" );
break ;
}
}
}
}
结构体与面向对象范式
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
struct Circle {
x : f64 ,
y : f64 ,
radius : f64 ,
}
impl Circle {
fn new ( x : f64 , y : f64 , radius : f64 ) -> Circle {
Circle {
x : x ,
y : y ,
radius : radius ,
}
}
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 ());
// use associated function and method chaining
println ! ( "{}" , Circle ::new ( 0.0 , 0.0 , 2.0 ). area ());
}
关联函数(静态方法) 通常构造函数用此来实现
与Go对比
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
package main
import (
"fmt"
"math"
)
type Circle struct {
x float64
y float64
radius float64
}
func NewCircle ( x , y , radius float64 ) * Circle {
return & Circle {
x : x ,
y : y ,
radius : radius ,
}
}
func ( c Circle ) Area () float64 {
return math . Pi * ( c . radius * c . radius )
}
func main () {
c := & Circle {
x : 0.0 ,
y : 0.0 ,
radius : 2.0 ,
}
fmt . Println ( c . Area ())
fmt . Println ( NewCircle ( 0.0 , 0.0 , 2.0 ). Area ())
}
元组结构体
在参数个数较少时,无字段名称,仅靠下标也有很强的语义时,为每个字段命名就显得多余了。例如:
1
2
3
4
5
struct Color ( i32 , i32 , i32 );
struct Point ( i32 , i32 );
let black = Color ( 0 , 0 , 0 );
let origin = Point ( 3 , 4 );
trait与泛型
用于接口抽象与泛型约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Duck ;
struct Pig ;
trait Fly {
fn fly ( & self ) -> bool ;
}
impl Fly for Duck {
fn fly ( & self ) -> bool {
return true ;
}
}
impl Fly for Pig {
fn fly ( & self ) -> bool {
return false ;
}
}
fn can_fly < T :Fly > ( s : T ) -> bool {
s . fly ()
}
泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn largest < T > ( list : & [ T ]) -> T {
let mut largest = list [ 0 ];
for & item in list . iter () {
if item > largest {
largest = item ;
}
}
largest
}
fn main () {
let number_list = vec ! [ 34 , 50 , 25 , 100 , 65 ];
let result = largest ( & number_list );
println ! ( "The largest number is {}" , result );
let char_list = vec ! [ 'y' , 'm' , 'a' , 'q' ];
let result = largest ( & char_list );
println ! ( "The largest char is {}" , result );
}
枚举与错误处理
无参数枚举体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Number {
Zero ,
One ,
Two ,
}
fn main () {
let a = Number ::One ;
match a {
Number ::Zero => println ! ( "0" ),
Number ::One => println ! ( "1" ),
Number ::Two => println ! ( "2" ),
}
}
类C枚举体
1
2
3
4
5
enum Color {
Red = 0xff0000 ,
Green = 0x00ff00 .
Blue = 0x0000ff ,
}
带参数枚举体
1
2
3
4
5
6
7
8
enum IpAddr {
V4 ( u8 , u8 , u8 , u8 ),
V6 ( String ),
}
let home = IpAddr ::V4 ( 127 , 0 , 0 , 1 );
let loopback = IpAddr ::V6 ( String ::from ( "::1" ));
使用泛型+枚举的两个示例
Option是标准库中定义的一个非常重要的枚举类型。它表示值的可能性。对Rust而言,变量在使用前必须要赋予一个有效的值,所以不存在空值(Null),如果一个值可能为空,需要显式地使用Option来表示。
1
2
3
4
enum Option < T > { // 通常用T来表示一个泛型,这就不用为每个枚举都定义一种类型
Some ( T ), // 有
None , // 无
}
unwarp可以取出some值,但如果是None会panic,实际是match的语法糖。
expect遇到none值会显示指定的异常消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std ::fmt ::Debug ;
fn match_option < T : Debug > ( o : Option < T > ) { // 实现了Debug的trait才可以格式化打印。
match o {
Some ( i ) => println ! ( "{:?}" , i ), // 当T 为有效值时,才能够从 Some(T) 中取出 T 的值来使用
None => println ! ( "nothing" ),
}
}
fn main () {
let a : Option < i32 > = Some ( 3 );
let b : Option < char > = Some ( "hello" );
let c : Option < u32 > = None ;
match_option ( a ); // 3
match_option ( b ); // hello
match_option ( c ); // nothing
}
Rust中的错误处理是通过返回Result<T, E>
枚举进行的,它表示错误的可能性。
1
2
3
4
enum Result < T , E > {
Ok ( T ),
Err ( E ),
}
有两个泛型类型T E,它表达成功或失败 用于错误处理
将Result<T, E>作为函数返回值,强制开发者处理OK 和 Err两种类型。
1
2
3
4
5
use std ::fs ::File ;
fn open_file () -> Result < (), std ::io :Error > {
let f = File ::open ( "bar.txt" ) ? ; // ?语法糖 在出错时返回std::io:Error
OK (())
}
Rust内存管理机制
按照内存管理方式可将编程语言大致分为两类:手动内存管理类 和自动内存管理类 。
前者需要开发者手动使用malloc和free等函数显式管理内存,如C。后者使用GC(Garbage Collection, 垃圾回收)来对内存进行自动化管理,如GO, Java。
前者优势性能高,但容易写出内存安全问题的程序,如空指针、悬垂指针、double free等。
后者使用GC接管了开发者管理内存的任务,但为了安全引入了性能负担,GC的时候会引起“世界暂停”,不能作为系统级编程语言(GC是建立在虚拟内存抽象上的)。
有木有一门语言将两者性能结合起来?
Rust推崇安全与速度至上,它没有垃圾回收机制,却成功实现了内存安全 (memory safety)。
通过设计一道合理的机制,将内存安全交给编译器解决,没有GC,内存由编译器分配,Rust编译为LLVM IR 其中携带了内存的分配信息,在编译时就可以确定何时需要分配、释放内存。
内存泄漏不在内存安全范围内
Ownership
C语言使用内存
1
2
3
4
5
6
7
8
#include <stdio.h>
int main ()
{
int * p = ( int * ) malloc ( 100 )
printf ( "%p" , p );
free ( p );
return 0 ;
}
为缓解此问题C++引入了智能指针,即通过智能指针来描述所有权,实现了内存的半自动化管理。
1
2
3
4
5
6
7
8
9
#include <iostream>
#include <memory>
using namespace std ;
int main ()
{
unique_ptr < int > p ( new int ( 5 ));
cout << * p << endl ;
return 0 ;
}
使用变量的生命周期绑定资源的使用周期,这种资源管理的方式有一个术语叫RAII(Resource Acquisition Is Initialization),智能指针就是基于RAII机制来实现的。
在现代C++中RAII的机制是使用构造函数来初始化资源,使用析构函数来回收资源(在栈对象析构函数中释放堆内存)。
存在安全隐患的示例:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <memory>
using namespace std ;
int main ()
{
unique_ptr < int > p ( new int ( 5 ));
cout << * p << endl ;
auto other_p = move ( p );
cout << * p << endl ;
return 0 ;
}
空指针解引 segmehtation fault 语义上的 move 并没有静态检查
Rust在此基础上更进一步,将所有权的概念融入到语言中
1
2
3
4
5
6
fn main () {
let p = Box ::new ( 5 ); // 堆上分配内存
println ! ( "{}" , * p );
let other_p = p ;
println ! ( "{}" , * p );
}
编译不过
rust 数据默认move语义, 实现了drop trait,栈元素离开作用域时自动调用drop析构函数,用以释放指向的堆内存。
1
2
3
trait Drop {
fn drop ( & mut self );
}
什么样的数据不是move语义呢:除非它实现了 Copy trait, 即copy语义,什么样的数据类型会被实现copy语义呢?
值类型
存在栈上 如int bool等,值类型执行赋值操作时会自动赋值一个副本。
引用类型
数据存在堆上,栈上只存放指向堆中数据的地址(指针),如可变长字符串,vector。
1
2
3
4
5
fn main () {
let x = 5 ;
let y = x ; // 按位复制(栈复制)后,与原始数据无关,修改后不影响原值,不存在内存安全问题
// Rust不允许引用类型栈复制(两个指针指向同一个堆空间) 存在潜在的内存安全问题 因为引用类型默认move语义
}
会按位复制(栈复制) 是copy语义 实现了Copy trait,一旦某类型实现copy trait,那么它在变量绑定、函数参数传递、函数返回值等场景下都是copy语义,而不是默认的move语义。
并不是所有的类型都可以手动实现Copy trait,如为结构体实现 Copy trait,需该结构体的所有类型都是实现Copy trait的。实现了drop析构函数的是不能实现Copy trait的。
Rust通过此标记对值语义和引用语义做了精准分类,来帮助编译器检测潜在的内存安全问题。所有权管理堆内存,即引用类型,此类型才需要保证内存安全。
对于copy类型 按位复制不会出现安全问题,对于禁止实现copy的类型,按位复制可能出问题,所以默认实现move语义,且通过RAII机制自动释放堆内存。
所有权规则:
堆内存上的数据类型都有一个称为所有者的变量(栈上)
任一时刻(生存周期)有且只有一个所有者
当所有者变量离开作用域,堆上的数据被自动释放
1
2
3
4
5
6
{
let s = String ::from ( "hello" ); // 从此处起,s 是有效的
// 使用 s
} // 此作用域已结束,
// s 不再有效
1
2
3
4
let s1 = String ::from ( "hello" );
let s2 = s1 ; //一个值的所有权被转移给另外一个变量绑定的过程就叫所有权转移
println ! ( "{}, world!" , s1 ); // use of moved value: `s1`
// 想复制堆上数据 可以用clone
所有权与函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main () {
let s = String ::from ( "hello" ); // s 进入作用域
takes_ownership ( s ); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5 ; // x 进入作用域
makes_copy ( x ); // x 应该移动函数里,
// 但 i32 是 Copy 的,所以在后面可继续使用 x
}
fn takes_ownership ( some_string : String ) { // some_string 进入作用域
println ! ( "{}" , some_string );
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
fn makes_copy ( some_integer : i32 ) { // some_integer 进入作用域
println ! ( "{}" , some_integer );
} // 这里,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 (); // gives_ownership 将返回值
// 移给 s1
let s2 = String ::from ( "hello" ); // s2 进入作用域
let s3 = takes_and_gives_back ( s2 ); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 移出作用域并被丢弃
fn gives_ownership () -> String { // gives_ownership 将返回值移动给
// 调用它的函数
let some_string = String ::from ( "hello" ); // some_string 进入作用域.
some_string // 返回 some_string 并移出给调用的函数
}
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back ( a_string : String ) -> String { // a_string 进入作用域
a_string // 返回 a_string 并移出给调用的函数
}
主动释放
1
2
3
4
5
6
7
use std ::mem ::drop ;
fn main () {
let mut v = vec ! [ 1 , 2 , 3 ]; // <--- v的生命周期开始
delete ( v ); // ---> v的生命周期结束
v . push ( 4 ); // 错误的调用
}
1
2
3
fn drop < T > ( _x T ) {
}
思考:标准库中的std::mem::drop函数是怎样实现的呢?
1
2
3
4
5
6
7
8
use std ::mem ::drop ;
fn main () {
let x = 1_ i32 ;
println ! ( "before drop {}" , x );
drop ( x );
println ! ( "after drop {}" , x );
}
Borrow(reference)
在每一个函数中都获取所有权并接着返回所有权太繁琐。
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 (); // len() 返回字符串的长度
( s , length )
}
如果我们想要函数使用一个值但不获取所有权该怎么办呢?
定义一个新的calculate_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 ()
}
取指针被称为借用, 语义上告诉编译器 没有对这块内存的所有权,只是借来用,离开作用域后不会drop堆上内存,且要归还。
可变与不可变借用
1
2
3
4
5
6
7
8
9
fn main () {
let s = String ::from ( "hello" );
change ( & s );
}
fn change ( some_string : & String ) {
some_string . push_str ( ", world" );
}
cannot borrow immutable borrowed content *some_string
as mutable
正如变量默认不可变,借用默认也不可修改
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" );
}
借用有以下几个规则:
&mut型借用只能指向本身具有mut修饰的变量,对于只读变量,不可以有&mut型借用。
&mut型借用指针存在的时候,被借用的变量本身会处于“冻结”状态。
1
2
3
4
5
6
let mut s = String ::from ( "hello" );
let r1 = & mut s ;
s . push_str ( ", world" );
println ! ( "{}, {}" , r1 , s );
借用指针不能比它指向的变量存在的时间更长。 避免悬垂引用
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
}
如果只有&型借用指针,那么能同时存在多个;如果存在&mut型借用指针,那么只能存在一个;一个可变引用不能与其他引用同时存在 避免数据竞争
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 );
一个引用的作用域从声明的地方开始一直持续到最后一次使用为止
1
2
3
4
5
6
7
8
9
let mut s = String ::from ( "hello" );
let r1 = & s ; // 没问题
let r2 = & s ; // 没问题
println ! ( "{} and {}" , r1 , r2 );
// 此位置之后 r1 和 r2 不再使用
let r3 = & mut s ; // 没问题
println ! ( "{}" , r3 );
可变引用保证了在写的时候没有任何指针可以读取该值的内存,不可变引用保证了内存不会在读取之后被写入新数据。
核心原则
共享不可变,可变不共享
Rust不是针对各式各样的场景,用case by case的方式来解决内存安全问题。而是通过一种统一的机制,高屋建瓴地解决这一类问题。
多个读同时存在是可以的,存在一个写的时候,其他的读写都不能同时存在。
&mut型借用也经常被称为“独占指针”, &型借用也经常被称为“共享指针”。
例1 共享不可变一定安全
1
2
3
4
5
6
let s = String ::from ( "hello" );
let p1 = & s ;
let p2 = & s
println ! ( "{}, {}" , p1 , p1 );
例2 共享后不可变
1
2
3
4
5
let mut s = String ::from ( "hello" );
let r1 = & mut s ;
s . push_str ( ", world" );
println ! ( "{}, {}" , r1 , s ); // r1是这时唯一可以操作内存的入口
例3 同时多个可变引用
1
2
3
4
5
6
let mut s = String ::from ( "hello" );
let mut p1 = & s ;
let mut p2 = & s
println ! ( "{}, {}" , p1 , p1 );
例4 与cpp对比的一个🌰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>
using namespace std ;
int main ()
{
vector < int > v ( 5 , 5 );
int * p = & v [ 0 ];
cout << * p << endl ;
for ( int i = 0 ; i < 100 ; i ++ )
{
v . push_back ( i );
}
cout << * p << end ;
return 0 ;
}
1
2
3
4
5
6
7
8
fn main () {
let mut arr : vec < i32 > = vec ! [ 5 , 5 , 5 , 5 , 5 ]
let p : & i32 = & arr [ 0 ];
for i in 1 .. 100 {
arr . push ( i );
}
print ! ( p )
}
error: cannot borrow arr
as mutable be cause it is also borrowed as immutable
在存在一个不可变引用时不能修改原变量的值