Rust学习笔记
Rust是一种令人兴奋的新编程语言, 它可以让每一个人编写可靠且高效的软件.它可以用来替换C/C++, Rust和他们具有同样的性能, 但是很多常见的bug在编译时就可以被消灭.Rust是一种通用的编程语言, 但是它更善于以下场景:需要运行时的速度需要内存安全更好的利用多处理器
与其他语言比较C/C++ 性能非常好, 但类型系统和内存都不太安全.JAVA/C#, 拥有GC, 能保证内存安全, 也有很多优秀特性, 但是性能不行RUST:安全无需GC易于维护, 调试, 代码安全高效
Rust特别擅长的领域高性能webservicewebassembly命令行工具网络编程嵌入式设备系统编程
Rust与FirefoxRust最初是Mozilla公司的一个研究性项目, firefox是Rust产品应用的一个重要例子.Mozilla 一直以来都在用rust创建一个名为servo的实验性浏览器引擎, 其中的所以内容都是并行执行的.目前servo的部分功能已经被集成到firefox里面了firefox原来的量子版就包含了servo的css渲染引擎
rust使得firefox在这方便得到了巨大的性能改进Rust的用户和案例google: 新操作系统Fuschia, 其中Rust代码量大约30%Amazon: 基于Linux开发的直接可以在裸机, 虚拟机上运行容器的操作系统.System76: 纯Rust开发了下一代安全操作系统Redox蚂蚁金服: 库操作系统Occlum斯坦福和密歇根大学: 嵌入式实时操作系统, 应用于google的加密产品.微软: 正在使用Rust重写windows系统中的一些低级组件.微软: winRT/Rust项目Dropbox, yelp, Coursera, LINE, Cloudflare, Atlassian, npm, Ceph, 百度, 华为, Sentry, DenoRust的优点性能安全性无所畏惧的并发Rust的缺点难学注意Rust有很多独有的概念, 它们和现在大多主流语言都不同.
所以学习Rust必须从基础概念一步一步学, 否则会懵.参考教材:The Rust programming languageRust权威指南
安装Rust安装rust官网: https://www.rust-lang.orgLinux or Mac:curl https://sh.rustup.rs -sSf| shwindows: 按官网指示操作windows subsystem for Linux: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
更新Rust rustup update
卸载Rust rustup self uninstall
验证安装是否成功 rustc --version
结果格式: rustc x.y. z (abcabcabc yyyy-mm-dd)会显示最新稳定版的: 版本号, commit hash, commit 日期第二章:第一个rust程序hello worldfn main() { println!("hello world");}
编译:
rustc main.rs
编译完成后将会输出 可执行文件main运行程序使用
./main
Rust 程序解析定义函数 fn main() {}没有参数, 没有返回值main 函数很特别 : 他是每一个Rust 可执行程序 最先运行的代码打印文本: println!("hello, world!")rust 的缩进是4个空格而不是tabprintln! 是一个Rust macro(宏)如果是函数的话就没有!"hello world" 是字符串, 它是println!的参数这行代码以;结尾编译和运行是单独的两步运行rust程序之前需要先编译, 命令为: rustc 源文件名 rustc main.rs
编译成功后, 会生成一个二进制文件在wendow上还会生成一个.pdb文件, 里面包含调试信息Rust是 ahead-of-time 预编译的语言可以先编译程序, 然后把可执行文件交给别人运行(无需安装Rust)rustc只适合简单的Rust程序当文件比较多, 项目比较多是需要使用Cargohello cargocargo 介绍Cargo 是Rust的构建系统和包管理工具构建代码, 下载依赖的库, 构建这些库安装Rust的时候会安装Cargocargo --version$cargo --versioncargo 1.55.0 (32da73ab1 2021-08-23)
使用Cargo创建项目创建项目: cargo new hello_cargo
创建完成后项目的目录结构
$tree -a . hello_cargo├── Cargo.toml├── .git│ ├── config│ ├── description│ ├── HEAD...................├── .gitignore└── src └── main.rs
目录结构说明:Cargo.toml:
TOML(Tom's obvious, Minimal Language)格式,是Cargo的配置格式 [package] name="hello_cargo" version="0.1.0" edition="2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
[package], 是一个区域标题, 表示下方内容是用来配置包(package)的.
name: 项目名version: 项目版本authors: 项目作者[dependencies], 另一个区域的开始,它会列出项目的依赖项.
在Rust里面,代码的包称作crate(板条箱;篓子;)
src/main.js
cargo生成的main.rs在src目录下而Cargo.toml在项目顶层下源代码都应该在src目录下顶层目录可以放置:README, 许可信息, 配置文件和其它与程序源代码无关的文件如果创建项目时没有使用cargo, 也可以把项目转化为使用cargo:把源代码文件移动到src下创建Cargo.toml并填写相应的配置构建Cargo项目cargo build创建可执行文件:target/debug/hello_cargo (linux/macos)或者 target\debug\hello_cargo.exe (windows)第一次运行cargo build会在顶层目录生成cargo.lock文件该文件负责追踪项目依赖的精确版本不需要手动修改该文件构建和运行cargo项目cargo runcargo run, 编译代码+执行结果 - 如果之前编译成功过, 并且源代码没有改变, 那么就会直接运行二进制文件cargo checkcargo check, 检查代码, 确保能通过编译, 但是不产生任何可执行文件cargo buid -release编译时会进行优化 - 代码会运行的更快, 但是编译时间更长 - 会在target/release 而不是target/debug生成可执行文件 - 两种配置, 一种是开发用的, 一种是发布用的猜数字游戏声明变量main.rs
println!("猜数!"); println!("猜测一个数!"); //let声明一个变量 //mut 声明变量为可变变量, // 默认情况下变量是不可变的, 除非显示使用mut指明变量为可变变量 //=赋值操作 //注意申明时没有指定变量类型, 变量类型是根据赋初始值时进行推导的 //String是由rust 的标准库所提供的类型,内部是使用utf-8编码 //:: 符号表明new是String类型的关联函数,关联函数相当与其他语言中的静态方法 let mut guess=String::new(); //io是rust标准库中的一个包名 //stdin()方法会返回一个Stdin对象, 标准输入对象 //read_line 是标准输入对象的一个方法,调用该方法时,需要提供一个可变字符串变量,用于接收用户输入 //& 取地址符号,表示传递引用, 表示这个参数是一个引用reference,通过引用我们就可以在不同地方,访问程序的统一块内存区域 //&mut表示这个引用是可变的, 如果不加mut, 表明这个引用也是不可变的 //read_line函数返回的是一个i0:Result<usize>对象,expect是result对象的一个方法 // result是一个枚举类型, 其有两种类型的返回结果, 一种是err, 一种ok // 如果返回的result为err, 该expect就会将错误信息输出到终端 //如果返回结果是ok类型, expect就会提取出result中附加的值并将这个值作为结果返回给用户 //如果不调用expect方法, 编译时将会收到, rusult未被使用的警告 io::stdin().read_line(&mut guess).expect("无法读取行"); //{} 是一个占位符,输出时将会替换成相对应的变量的值 println!("你猜测的数时{}", guess);
引入依赖rust中的依赖被称为cratecrate分为两种, 一种是二进制格式的可执行文件, 一种是源文件, 这种crate被称为library crate
rust的crate仓库为crates.io, 可以访问该网站获得相应的crate
cargo 引入依赖的方式
#Cargo.toml[package]name="hello_cargo"version="0.1.0"edition="2018"[dependencies]rand="^0.7.0"
在dependencies 区域添加依赖的crate名称和版本如上所示.
示例代码use std::io;use rand::Rng; //traituse std::cmp::Ordering;fn main() { println!("猜数!"); let secret_num=rand::thread_rng().gen_range(1, 101); //rust循环 loop { println!("猜测一个数!"); let mut guess=String::new(); io::stdin().read_line(&mut guess).expect("无法读取行"); println!("你猜测的数是: {}", guess); //shadow let guess:u32=match guess.trim().parse() { Ok(num)=> num, Err(ex)=> { //rust异常处理 println!("解析错误 {} {}", guess, ex); continue; } }; // rust条件运算 match guess.cmp(&secret_num) { Ordering::Less=> println!("Too small!"), //arm Ordering::Greater=> println!("Too big!"), Ordering::Equal=> { println!("You win!"); break; } } } }
第三章 通用的编程概念变量和可变性声明变量使用let 关键字
默认情况下,变量是不可变的(immutable)例如
let x=5; // x 为不可变变量 println!("the value of x is {}", x); x=6; // 注意: 这里会有编译错误 println!("the value of x is {}", x);
声明变量时, 在变量前面加上mut, 就可以使变量可变.
let mut x=5; println!("the value of x is {}", x); x=6; println!("the value of x is {}", x);
变量和常量常量(constant), 常量在绑定值以后也是不可变的, 但是它与不可变的变量有很多区别: - 不可以使用mut, 常量永远都是不可变的 - 声明常量使用const关键字, 它的类型必须被标注 - 常量可以在任何作用域内进行声明, 包括全局作用域 - 常量只可以绑定到常量表达式, 无法绑定到函数的调用结果或只能在运行时才能计算出的值 - 在程序运行期间, 常量在其声明的作用域内一直有效 - 命名规范: Rust里常量使用全大写字母, 每个单词之间用下划线分开, 例如: const MAX_POINTS: u32=100_000;
shadowing (隐藏)可以使用相同的名字声明新的变量, 新的变量就会shadow(隐藏)之前声明的同名变量在后续代码中这个变量名代表的就是新的变量shadow和把变量标记为mut是不一样的例如 fn main() { //定义不可变变量x let x=5; println!("the value of x is {}", x); // x=6 // 如果这里给x赋值会报错 // 但是如果我们声明一个同名的新的变量,就可以编译通过 // 我们甚至可以改变x的数据类型, 甚至可以定义新的同名但是不同的可变性的变量 //新的同名变量 let x=6; println!("the value of x is {}", x); //不同可变性的同名变量 let mut x="my love"; println!("the value of x is {}", x); x="hello kitty"; println!("the value of x is {}", x); }
Rust数据类型标量和复合类型
Rust 是静态编译语言, 在编译时必须知道所有变量的类型
基于使用的值, 编译器通常能够推断出它的具体类型但如果可能的类型比较多(例如把String 转为整数的parse方法),就必须添加类型的标注,否则编会报错.例如 let guess: u32="42".parse().expect("not a number") println!("{}", guess)
标量类型一个标量类型代表一个单个的值Rust有四个主要的标量类型:整数类型浮点类型布尔类型字符类型整数类型- 整数类型分为无符合整数类型, - 无符合整数类型以u开头- 有符号整数类型以i开头
Rust 的整数类型列表如图:
每种长度都有对应的有符号型和无符号型.
有符号范围 -(2的n-1次方-1) 到(2的n-1次方-1)
无符号范围 - 0 到2的n次方 -1 - isize 和 usize类型
isize和usize类型的位数由程序运行的计算机的架构所决定如果是64位的计算机,那就是64位的如果是32位的计算机,那就是32位的使用isize或者usize的场景是对某种集合进行索引操作整数的字面值 - 除了byte类型外, 所有的数值字面值都允许使用类型后缀 例如: 57u8: 值为57 类型为u8
整数的默认类型就是i32:总体来说速度很快, 即使在64位系统中 | NumberLiteral | Example | |:----|:----| | Decimal | Oxff | | Hex | Oo77 | | Binary | Ob1111_0000 | | Byte (u8 only) | b'A' |- 整数溢出 例如:u8的范围是0-255, 如果你把一个u8变量的值设为256, 那么: - 调试模式下编译:rust会检查整数溢出, 如果发生溢出, 程序运行时就会panic - 在发布模式下(--release)编译:rust不会检查可能导致panic的整数溢出 - 在这种模式下如果发生溢出:rust会执行环绕操作 256变成0, 257变成1....- 但是不会导致panic
浮点类型rust 有两种基础的浮点类型,也就是含有小数部分的类型f32, 32位, 单精度f64, 64位,双精度rust的浮点类型使用了IEEE-754标准来表述f64是默认类型,因为在现代cpu上f64和f32的数度差不多,而且精度更高例子 let x=2.0 //默认为f64 let y: f32=3.0; //f32
数值操作```rust let sum=5+10; let difference=95.5-4.3; //f64 let product=4*30; let quotient=56.7/32.2 let reminder=54%5 ```
布尔类型字符类型rust语言中char类型被用来描述语言中最基础的单个字符.字符类型的字面值使用单引号占用4字节大小是Unicode 标量值, 可以表示比ASCII多得多的字符内容:拼音, 中日文, 零长度空白字符,emoji表情等.
其范围为U+0000到U+D7FFU+E000到U+10FFFF但是unicode中并没有字符的概念, 所以自觉上认为的字符也许与Rust中的概念并不相符复合类型复合类型可以将多个值放在一个类型里Rust提供了两种基础的复合类型: 元组(Tuple), 数组TupleTuple可以将多个类型的多个值放在一个类型里Tuple的长度是固定的: 一旦声明就无法改变创建tuple在小括号里, 将值用逗号分开Tuple中的每个位置都对应一个类型,tuple中各元素的类型不必相同实例 let tup: (i32, f64, u8)=(500, 6.4, 1) println!("{},{},{}", tup.0, tup.1, tup.2)
获取tuple的元素值可以使用模式匹配来解构(destructure)一个Tuple来获取元素的值例子 let tup: (i32, f64, u8)=(500, 6.4, 1); let (x, y, z)=tup; //这里使用模式匹配解构tup的值 println!("{},{},{}", x, y, z)
访问tuple的元素在tuple变量使用点标记法,后接元素的索引号实例 let tup: (i32, f64, u8)=(500, 6.4, 1) println!("{},{},{}", tup.0, tup.1, tup.2)
数组数组也可以将多个值放在一个类型里数组中每个元素的类型必须相同数组的长度也是固定的声明一个数组在中括号里, 各值用逗号分开例子 fn main() { let a=[1, 2, 3, 4, 5] }
数组的用处如果想让你的数据存放在stack上, 而不是heap上,或者想保证有固定数量的元素, 这时使用数组更有好处.
数组没有Vector灵活(以后再讲)
Vector和数组类似,它是由标准库提供的Vector的长度是可以改变的如果你不确定应该使用数组还是vector, 那么估计你应该用vector.例子
fn main() { let months=["January", "Fabruary", ...... "December" ] }
数组的类型数组的类型以这种形式来表示: [类型; 长度]例如: let a: [i32; 5]=[1, 2, 3, 4, 5];另外一种声明数组的方法 如果数组的没一个元素值都相同, 那么可以在:在中括号里指定初始值然后是一个;最后是数组的长度例如: let a=[3; 5]; 它相当于:let a=[3, 3, 3, 3, 3]访问数组的元素数组是stack上分配的单个块的内存
可以使用索引来访问数组的元素
例子
let first=months[0]; let second=months[1];
如果访问的索引超出了数组的范围, 那么
编译会通过运行会报错(runtime时会panic)Rust不会允许其继续访问相应地址的内存说明:在简单的情况下,编译会报错,但是在复杂情况下,编译不会报错例如 let index=15; let month=months[index]; //此时编译会报错
let index=[15, 1, 2 , 3]; let month=months[index[0]]; //此时编译不会报错, 运行时会发生panic
3.4 函数声明函数使用fn关键字依照惯例,针对函数和变量名,rust使用snake case命名规范:所有的字母都是小写的, 单词之间使用下划线分开例子fn main() { println!("hello world"); another_function();}fn another_function() { println!("another function")}
函数的参数parameters, arguments例子fn main() { println!("hello world"); another_function(5);}fn another_function(x: i32) { println!("the value of x is {}", x)}
函数中的语句(statement)和表达式(expression)函数体由一系列语句组成, 可选的由一个表达式结束Rust是一个基于表达式的语言语句是执行一些动作的指令表达式会计算产生一个值函数的定义也是语句语句不返回值,所以不可以使用let将一个语句-赋给一个变量例子
fn main() { let x=5; let y={ let x=1; //x+3; //注意这里有个分号,它为语句而不是表达式, 但是这个语句有些特殊 它的值等于一个空的tuple 即() x+3 //这里没有分号, 它是一个表达式, 表达式的值为5 它是整个block的返回值, 该值将会被赋给变量y } }
函数的返回值在->符号后边声明函数返回值的类型, 但是不可以为返回值命名在Rust里面, 返回值就是函数体里面最后一个表达式的值若想提前返回, 需使用return 关键字, 并指定一个值大多数函数都是默认使用最后一个表达式为返回值例子: fn five() -> i32 { 5 } fn main() { let x=five(); println("the value of x is: {}", x); }
注释单行注释多行注释文档注释
控制流if 表达式if 表达式允许你根据条件来执行不同的代码分支这个条件必须是bool类型if 表达式中, 与条件相关联的代码块就叫做分支(arm)可选的, 在后面可以加上一个else表达式但是如果使用了多余一个else if, 那么最好使用match来重构代码例子 fn main() { let number=3; if number < 5 { println!("condition was false"); } else { println!("condition was false"); } }
例子:else if fn main() { let number=3; if number % 4==0 { println!("number is divisaible by 4"); } else if number % 3==0 { println!("number is divisaible by 3"); } else { println!("number is not divisaible by 3 or 4"); } }
在let语句中使用if因为if是一个表达式, 所以可以将它放在let语句中等号的右边(例子) fn main() { let condition=ture; let muber=if condition {5} else {6}; println!("The value of number is {}", number); }
Rust的循环Rust提供了3中循环:loop, while 和 for.loop 循环loop关键字告诉Rust反复地执行一块代码,直到你喊停位置可以在loop循环中使用break关键字来告诉程序何时停止循环例子
fn main() { let mut counter=0; let resut=loop { counter +=1; if counter==10 { break counter * 2; } } println!("The result is: {}", result); }
while条件循环另外一种常见的循环模式是每次执行循环体之前都判断一次条件.
while条件循环就是为这种模式而生的
例子
fn main() { let mut number=3; while number !=0 { println!("{}!", number); number=number -1; } println!("LIFTOFF!!!"); }
for 循环遍历集合可以使用while 或 loop 来遍历机会, 但是易出错且低效.
使用for循环更简洁紧凑, 它可以针对集合中的每一个元素来执行一些代码
例子
fn main() { let a=[10, 20, 30, 40, 50] for element in a.iter() { println!("the value is: {}", element); }}
由于for循环的安全,简洁性,所以它在Rust里用的最多
Range标准库提供指定一个开始数字和一个结束数字,Range可以生成它们之间的数字(不包含介绍)rev方法可以反转range例子 fn main() { for number in (1..4).rev() { println!("{}!", number); } println!("LIFTOFF!"); }
第四章: 所有权4.1 什么是所有权Rust的核心特性就是所有权所有程序在运行时都必须管理它们使用计算机内存的方式 - 有些语言有垃圾收集机制,在程序运行时, 它们不断地寻找不再使用的内存 - 在其他语言中,程序员必须显示地分配和释放内存 - Rust采用了第三种方式 - 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则. - 当程序运行时,所有权特性不会减慢程序运行速度Stack vs HeapStack按值的接收顺序来存储,按相反的顺序将它们移除(后进先出, LIFO)添加数据叫做压入栈移除数据叫做弹出栈所有存储在stack上的数据必须拥有已知的固定的大小编译时大小未知的数据或运行时大小可能变化的数据必须存放在heap上Heap内存组织性差一些当你把数据放入heap时, 你会请求一定数量的空间操作系统在heap里找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址这个过程叫做在heap上进行分配, 有时仅仅称为分配把值压到stack上不叫分配因为指针是已知固定大小的, 可以把指针存放在stack上. 但如果想要实际数据,你必须使用指针来定位把数据压倒stack上要比在heap上分配快得多: - 因为操作系统不需要寻找用来存储新数据的空间,那个位置永远在stack的顶端在heap上分配空间需要做更多的工作: - 操作系统首先需要找到一个足够大的空间来存放数据,然后要做好记录方便下次分配 - 访问heap中的数据要比访问stack中的数据慢,因为需要通过指针才能找到heap中的数据 - 对于现代的处理器来说, 由于缓存的缘故,如果指令在内存中跳转的次数越少,那么速度就越快. - 如果数据存放的距离比较近,那么处理器的处理速度就会更快一些(stack上) - 如果数据之间的距离比较远,那么处理速度就会慢一些(heap上) - 在heap上分配大量的空间也是需要时间的 - 当你的代码调用函数时,值被传入到函数(也包括指向heap的指针).函数本地的变量被压到stack上,当函数结束后,这些值会从stack上弹出.所有权存在的原因所有权解决的问题跟踪代码的哪些部分正在使用heap的哪些数据最小化heap上的重复数据量清理heap上未使用的数据以避免空间不足一旦你懂得了所有权,那么就不需要经常去想stack或heap了但是知道管理heap数据是所有权存在的原因,这有助于解释它为什么会这样工作.所有权,内存与分配所有权规则每个值都有一个变量,这个变量是该值的所有者每个值同时只能有一个所有者当所有者超出作用域的时候,该值将被删除.变量作用域scope就是程序中一个项目的有效范围
例子
fn main() { //s 不可以 let s="hello"; // s可用 //可以对s进行相关操作 } //s 作用域到此结束,s不再可用
string 类型String 比那些基础标量数据类型更复杂字符串字面值: 程序里手写的那些字符串值.它们是不可变的Rust还有第二种字符串类型: String在heap上分配,能够存储在编译时未知数量的文本创建String类型的值可以使用from函数从字符串字面值创建出String类型 let s=String::from("hello");// :: 表示from是String类型下的函数
这类字符串是可以被修改的
fn main() { let mut s=String::from("Hello"); s.push_str(", world"); pringln!(s); }
字符串字面值,在编译时就知道它的内容, 其文本内容直接被编码到最终的可执行文件里
速度快,高效,得益于其不可变性String类型,为了支持可变性, 需要在heap上分配内存来保存编译时未知的文本内容:
操作系统必须在运行时来请求内存这步通过调用String::from来实现当用完String之后,需要使用某种方式将内存返回给操作系统这步,在拥有GC的语言中,GC会跟踪并清理不再使用的内存没有GC的语言中,就需要我们去识别内存何时不再使用,并调用代码将它返回如果忘了,那就浪费内存.如果提前做了,变量就会非法如果做了两次,也是bug. 必须一次分配对应一次释放rust采用了不同的方式: 对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交换给操作系统.drop函数变量和数据交互的方式: 移动 move多个变量可以与同一个数据使用一种独特的方式来交互
let x=5;let y=x;
整数是已知且固定大小的简单的值, 这两个5被压到了stack中
let s1=String::from("hello"); let s2=s1;
一个String 由3部分组成:
一个指向存放字符串内容的内存的指针一个长度一个容量上面这些东西被放在stack上.
存放字符串内容的部分在heap上
长度len, 就是存放字符串内容所需的字节数
当把s1 赋值给S2, String 的数据被复制了一份
在stck上复制了一份指针, 长度, 容量
并没有复杂指针所指向的heap上的数据
当变量离开作用域时,Rust会自动调用Drop函数,并将变量使用的heap内存释放. 当S1, S2离开作用域时, 它们都会尝试释放相同的内存
二次释放(double free) bug为了保证内存安全:
Rust没有尝试复制被分配的内存
Rust让s1失效
当s1离开作用域的时候, rust不需要释放任何东西
试试看当s2创建后,再使用s1是什么效果
fn main() { let s1=String::from("hello"); let s2=s1; println!("{}", s1); //这里会有编译错误,这里s1已经失效了 }
浅拷贝(shallow copy)深拷贝(deep copy)你也许会将复制, 指针, 长度, 容量视为浅拷贝, 但是由于Rust让s1失效了, 所以我们用一个新的术语:移动move隐藏了一个设计原则,Rust不会自动创建数据的深拷贝 - 就运行时性能而言, 任何自动赋值的操作都是廉价的变量和数据交互的方式: 克隆(Clone)如果真想对heap上面的String 数据进行深拷贝, 而不仅仅是stack上的数据,可以使用clone方法
fn main() { let s1=String::from("hello"); let s2=s1.clone(); println!("{}", s1); }
Stack上的数据: 复制Copy trait, 可以勇于想整数这样完全放在stack上面的类型如果一个类型实现了Copy这个trait,那么旧的变量在赋值后仍然可用如果一个类型或者该类型的一部分实现了Drop trait,那么Rust不允许让它再去实现Copy trait了一些拥有copy trait的类型任何简单标量的组合类型都可以是copy的任何需要分配内存或某种资源的都不是copy的一些拥有Copy trait的类型 - 所有的整数类型, 例如u32boolchar所有的浮点类型, 例如f64tuple 元组, 如果其所有的字段都是Copy的 (i32, i32) 是(i32, String) 不是所有权与函数在语义上, 将值传递给函数和把值赋给变量是类似的:将值传递给函数将发生移动或复制返回值与作用域函数在返回值的过程中同样也会发生所有权的转移 fn main() { let s1=gives_ownership(); let s2=String::from("hello"); let s3=take_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 }
一个变量的所有权总是遵循同样的模式:把一个值赋给其它变量时就会发生移动当一个包含heap数据的变量离开作用域时,它的值就会被Drop函数清理,除非数据的所有权移动到另一个变量上了如何让函数使用某个值,但是不获得其所有权?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)}
引用和借用一下例子中参数的类型是&String而不是String&符号就表示引用:允许你引用某些值而不取得所有权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) -> usize { s.len()}
我们把引用作为函数参数这个行为叫做借用是否可以修改借用的东西?不行和变量一样,引用默认也是不可变的
可变引用可变引用有一个重要的限制: 在特定作用域内,对某一块数据, 只能有一个可变引用
这样做的好处是可在编译时防止数据竞争以下三种行为下会发生数据竞争:两个或多个指针同时访问同一个数据至少有一个指针用于写入数据没有使用任何机制来同步对数据的访问可以通过创建新的作用域, 来允许非同时的创建多个可变引用(例子)不可以同时拥有一个可变引用和一个不变的引用fn main() { let mut s1=String::from("hello"); let len=calculate_length(&mut s1); println!("Then length of '{}' is {}.", s1, len);}fn calculate_length(s: &mut String) -> usize { s.push_str(", world") s.len()}
悬空引用Dangling References悬空指针(Dangling Pointer): 一个指针引用了内存中的某个地址, 而这块内存可能已经释放并分配给其它人使用了.在Rust里, 编译器可保证引用永远都不是悬空引用:如果你引用了某些数据, 编译器将保证在引用离开作用域之前数据不会离开作用域 fn main() { let r=dangle(); } fn dangle() -> &String { // 这里编译器会报错,因为s出了此作用域将会被是放,而返回值是一个指向已经被释放区域的指针,这会导致问题,而rust在编译期就杜绝了这种错误. let s=String::from("hello"); &s }
Rust的代码组织
代码组织主要包括:
哪些细节可以暴露,哪些细节是私有的作用域内哪些名称是有效的模块系统:
Package(包):它是最顶层的。是Cargo的特性,让你构建、测试、共享crateCrate(单元包):一个模块树,它可以产生一个library或可执行文件Module(模块)、use:让你控制代码的组织、作用域、私有路径Path(路径):为struct、function或module等项目命名的方式Package和Crate
Crate的类型:
binary(二进制文件)library(库)Crate Root:
是源代码文件Rust编译器从这里开始,是入口。由它组成你的Crate的根Module一个Package:
包含1个Cargo.toml,它描述了如何构建这些Crates只能包含0-1个library crate可以包含任意数量的binary crate至少包含一个crate(library或binary)例,命令行创建一个project:
> cargo new my-project Created binary (application) `my-project` package
可以看到它创建了一个binary类型的crate,package是my-project。用vs code打开这个项目。可以看到里面都有:
Cargo的惯例
src/main.rs:
它是binary crate的crate rootcrate名与package名相同,即这里的binary crate和package都是my-project假设src下还有一个lib.rs src/lib.rs:
package包含一个library crate它是library crate的crate rootcrate名与package名相同,即这里的library crate和package都是my-projectCargo会把crate root文件交给rustc(Rust编译器)来构建library或binary。
一个Package可以同时包含src/main.rs和src/lib.rs:
一个binary crate,一个library crate它们的名字都与package相同一个Package可以有多个binary crate:
文件放在src/bin下面每个文件都是单独的binary crateCrate的作用
Crate将相关功能组合到一个作用域内,以便于在项目间进行共享,同时可以防止命名冲突。 例如提供随机数操作的rand crate,访问它的功能需要通过它的名字:rand
定义module来控制作用域和私有性
Module:
在一个crate内,将代码进行分组增加可读性,易于复用控制项目(item)的私有性。public、private建立module:
mod关键字module是可嵌套module的module可包含其它项(struct、enum、常量、trait、函数等)的定义例如在刚刚的项目中创建一个lib.rc:
mod front_of_house { mod hosting {//module可嵌套 fn add_to_waitlist() {}//可包含函数等其它项 fn seat_at_table() {} } mod serving { fn take_order() {} fn serve_order() {} fn take_payment() {} }}
以上代码组织结构如下图所示:
Rust模块树
src/main.rs和src/lib.rs叫做crate root。这两个源文件(任意一个)的内容形成了名为crate的模块,位于整个模块树的根部(如上图所示)。
上一篇:qq爱情留言大全
下一篇:qq邮箱的正确书写格式是什么?
发表评论