开始学习 Rust
写在前面
1. 第一个程序
2. 基本语法
2.1 注释
2.2 变量
2.3 函数
2.4 基本数据类型
2.5 操作符
2.6 流程控制
3. 其他数据类型
3.1 结构体(struct)
3.2 枚举
3.3 实现方法和接口(impl & trait)
3.4 泛型(Generics)
3.5 常见集合 Vec
3.6 常见集合 String
3.7 常见集合 HashMap
4 错误处理
4.1 不可恢复错误 panic!
4.2 可恢复错误 Result
5 包、crate和模块
5.1 包和 crate
5.2 模块
6 测试
6.1 单元测试(unit tests)
6.2 集成测试(integration tests)
7 生命周期
写在前面
rustup-init.exe 后运行安装即可。
1. 第一个程序
main.rs:
fn main() {
println!("Hello, world!");
}
rustc main.rs即可编译出一个可执行运行文件。
println 的一些用法:
fn main() {
println!("{}, {}!", "Hello", "world"); // Hello, world!
println!("{0}, {1}!", "Hello", "world"); // Hello, world!
println!("{greeting}, {name}!", greeting="Hello", name="world"); // Hello, world!
let y = String::from("Hello, ") + "world!";
println!("{}", y); // Hello, world!
}
2. 基本语法
2.1 注释
fn main() {
// 单行注释
println!("hello"); // 行尾注释
/*
块注释
*/
}
2.2 变量
mut 标记为可变变量。
局部变量
let a; // 声明变量,但并不赋值
let b = true; // 声明 bool 变量,并赋值
let c: bool = true;
let (x, y) = (1, 2);
a = 122;
let mut d = 1;
d = 20;
全局变量
static关键字申明全局变量,全局变量的生命周期是整个程序,必须显式标明类型,不支持类型推导;
// 不可变静态变量
static N: i32 = 64;
// 可变静态变量
static mut M: i32 = 10;
常量
const,和全局变量一样,生命周期也是整个程序。
const N: i32 = 10;
2.3 函数
fn来申明,例如:
fn test_func() {
println!("hi");
}
fn test_func(a: i32, b: i32) {
println!("the sum is:{}", a + b);
}
(),如果需要有返回值,需要在函数签名里使用->指定:
fn test_func(a: i32, b: i32) -> i32 {
println!("the sum is:{}", a + b);
a + b
// 等同于 return a + b;
}
fn plus_one(a: i32) -> (i32, i32) {
(a, &a + 1)
}
fn main() {
let (add_num, result) = plus_one(10);
println!("{} + 1 = {}", add_num, result); // 10 + 1 = 11
}
fn hello() {
println!("hello world!");
}
fn main() {
let b = hello;
b();
}
高阶函数
fn add_one(x: i32) -> i32 { x + 1 }
fn apply<F>(f: F, y: i32) -> i32
where F: Fn(i32) -> i32
{
f(y) * y
}
fn factory(x: i32) -> Box<Fn(i32) -> i32> {
Box::new(move |y| x + y)
}
fn main() {
let transform: fn(i32) -> i32 = add_one;
let f0 = add_one(2i32) * 2;
let f1 = apply(add_one, 2);
let f2 = apply(transform, 2);
println!("{}, {}, {}", f0, f1, f2);
let closure = |x: i32| x + 1;
let c0 = closure(2i32) * 2;
let c1 = apply(closure, 2);
let c2 = apply(|x| x + 1, 2);
println!("{}, {}, {}", c0, c1, c2);
let box_fn = factory(1i32);
let b0 = box_fn(2i32) * 2;
let b1 = (*box_fn)(2i32) * 2;
let b2 = (&box_fn)(2i32) * 2;
println!("{}, {}, {}", b0, b1, b2);
let add_num = &(*box_fn);
let translate: &Fn(i32) -> i32 = add_num;
let z0 = add_num(2i32) * 2;
let z1 = apply(add_num, 2);
let z2 = apply(translate, 2);
println!("{}, {}, {}", z0, z1, z2);
}
where 关键字的使用
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
// where 从句
fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
// 或者
fn foo<T, K>(x: T, y: K)
where T: Clone,
K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
发散函数
fn main() {
println!("hello");
diverging();
println!("world");
}
fn diverging() -> ! {
panic!("This function will never return");
}
2.4 基本数据类型
布尔值(bool)
字符(char)
有符号整型(i8, i16, i32, i64, i128)
无符号整型(u8, u16, u32, u64, u128)
指针大小的有符号/无符号整型(isize/usize,取决于计算机架构,32bit 的系统上,isize 等价于i32)
浮点数(f32, f64)
数组(arrays)
元组(tuples),由相同/不同类型元素构成,长度固定。
切片(slice),指向一段内存的指针。
let a: [i32; 4] = [1, 2, 3, 4];
let b: &[i32] = &a; // 全部
let c = &a[0..4]; // [0, 4)
let d = &a[..]; // 全部
let e = &a[1..3]; // [2, 3]
let e = &a[1..]; // [2, 3, 4]
let e = &a[..3]; // [1, 2, 3]
字符串(str)
let a = "Hello, world!"; //a: &'static str
let b: &str = "你好, 世界!";
// 多行字符串
let a = "line one
line two";
// 字符串转义
let a = "line one\nline two"
// 也可以在字符串字面量前加上r来避免转义
let a = r"line one\nline two"
[T;N]。其中N表示数组大小,并且这个大小一定是个编译时就能获得的整数值,
T表示泛型类型,即任意类型。我们可以这么来声明和使用一个数组:
let a = [8, 9, 10];
let b: [u8;3] = [8, 6, 5];
print!("{}", a[0]);
[u8; 3] != [u8; 4]。这么设计是为了更安全和高效的使用内存,当然了,
这会给第一次接触类似概念的人带来一点点困难,比如以下代码。
fn show(arr: [u8;3]) {
for i in &arr {
print!("{} ", i);
}
}
fn main() {
let a: [u8; 3] = [1, 2, 3];
show(a);
let b: [u8; 4] = [1, 2, 3, 4];
show(b);
}
show(b); 传入的参数类型是 [u8, 4],
但是show(arr: [u8, 3]) 这个函数签名只接受[u8,3]。
因此可以使用切片(Slice),一个Slice的类型是&[T] 或者 &mut [T], 因此以上代码可以改为:
fn show(arr: &[u8]) {
for i in arr {
print!("{} ", i);
}
println!("");
}
fn main() {
let a: [u8; 3] = [1, 2, 3];
let slice_a = &a[..];
show(slice_a);
let b: [u8; 4] = [1, 2, 3, 4];
show(&b[..]);
}
let s1 = "Hello, world!".to_string();
let s2 = String::from("Hello, world!");
函数(functions)
2.5 操作符
一元操作符
-: 取负,用于数值类型。
*: 解引用,这是一个很有用的符号,和Deref(DerefMut)这个trait关联密切。
!: 取反。
&和&mut: 租借,borrow。向一个owner租借其使用权,分别是租借一个只读使用权和读写使用权。
算数操作符
std::ops下:
+: 加法。实现了std::ops::Add。
-: 减法。实现了std::ops::Sub。
*: 乘法。实现了std::ops::Mul。
/: 除法。实现了std::ops::Div。
%: 取余。实现了std::ops::Rem。
// 包括: + - * / %
let a = 5;
let b = a + 1; //6
let c = a - 1; //4
let d = a * 2; //10
let e = a / 2; //2 not 2.5
let f = a % 2; //1
let g = 5.0 / 2.0; //2.5
比较运算符
std::cmp::PartialEq和std::cmp::PartialOrd
// == = != < > <= >=
let a = 1;
let b = 2;
let c = a == b; //false
let d = a != b; //true
let e = a < b; //true
let f = a > b; //false
let g = a <= a; //true
let h = a >= a; //true
let i = true > false; //true
let j = 'a' > 'A'; //true
逻辑运算符
// ! && ||
let a = true;
let b = false;
let c = !a; //false
let d = a && b; //false
let e = a || b; //true
// rust 的逻辑运算符是惰性boolean运算符
// @question 什么是惰性boolean运算符
位运算符
&: 与操作。实现了std::ops::BitAnd。
|: 或操作。实现了std::ops::BitOr。
^: 异或。实现了std::ops::BitXor。
<<: 左移运算符。实现了std::ops::Shl。
>>: 右移运算符。实现了std::ops::Shr。
// & | ^ << >>
let a = 1;
let b = 2;
let c = a & b; //0 (01 && 10 -> 00)
let d = a | b; //3 (01 || 10 -> 11)
let e = a ^ b; //3 (01 != 10 -> 11)
let f = a << b; //4 (左移 -> '01'+'00' -> 100)
let g = a >> a; //0 (右移 -> o̶1̶ -> 0)
赋值运算符
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
类型转换运算符: as
let a = 15;
let b = a / 2.0; //7.5
let b = (a as f64) / 2.0; //7.5
借用(Borrowing)与解引用(Dereference)操作符
// 引用/借用: & &mut
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len); // The length of 'hello' is 5.
}
fn calculate_length(s: &String) -> usize { // 获取引用作为函数参数称为借用
s.len()
}
// 解引用: *
fn main() {
// 获取v的第2个元素的可变引用,并通过解引用修改该元素的值。
let v = &mut [1, 2, 3, 4, 5];
{
let third = v.get_mut(2).unwrap();
*third += 50;
}
println!("v={:?}", v); // v=[1, 2, 53, 4, 5]
}
2.6 流程控制
if - else if - else
let team_size = 7;
if team_size < 5 {
println!("Small");
} else if team_size < 10 {
println!("Medium");
} else {
println!("Large");
}
// 条件块中有返回值时,类型需要一致,可替代C语言的三目运算符
let is_below_eighteen = if team_size < 18 { true } else { false };
match
let tshirt_width = 20;
let tshirt_size = match tshirt_width {
16 => "S", // check 16
17 | 18 => "M", // check 17 and 18
19 ... 21 => "L", // check from 19 to 21 (19,20,21)
22 => "XL",
_ => "Not Available",
};
println!("{}", tshirt_size); // L
_表示匹配剩下的任意情况。
match 的一些其他用法:
let num = 8;
match num {
13 => println!("正确"),
3|4|5|6 => println!("3 或 4 或 5 或 6" ),
14...20 => println!("14..20"),
// ... 表示一个范围,可以是数字, `1 ... 10` 也可以是字母 `'a' ... 'z'`
_=> println!("不正确"),
}
let pair = (0, -2);
match pair {
(0, y) => println!("y={}", y),
(x, 0) => println!("x={}", x),
_ => println!("default")
}
// 配合 if 使用
match num {
x if x != 20 => println!("14..20"),
_=> println!("不正确"),
}
// 后置套件
let x = 4;
let y = false;
match x {
4 | 5 if y => println!("yes"),
_ => println!("no"),
}
// 绑定变量
match num {
13 => println!("正确"),
3|4|5|6 => println!("3 或 4 或 5 或 6" ),
n @ 14...20 => println!("{}", n),
_=> println!("不正确"),
}
while
let mut a = 1;
while a <= 10 {
println!("Current value : {}", a);
a += 1; // Rust不支持++/--自增自减语法
}
loop
while true
let mut a = 0;
loop {
if a == 0 {
println!("Skip Value : {}", a);
a += 1;
continue;
} else if a == 2 {
println!("Break At : {}", a);
break;
}
println!("Current Value : {}", a);
a += 1;
}
// Skip Value : 0
// Current Value : 1
// Break At : 2
for
for a in 0..10 { //(a = 0; a <10; a++)
println!("Current value : {}", a);
}
'outer_for: for c1 in 1..6 { //set label outer_for
'inner_for: for c2 in 1..6 {
println!("Current Value : [{}][{}]", c1, c2);
if c1 == 2 && c2 == 2 { break 'outer_for; } // 结束外层循环
}
}
let group : [&str; 4] = ["Mark", "Larry", "Bill", "Steve"];
for person in group.iter() {
println!("Current Person : {}", person);
}
3. 其他数据类型
3.1 结构体(struct)
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
创建实例
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
修改某个字段的值
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");
变量与字段名同名的简写语法
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
元组结构体(tuple structs)
struct Color(i32, i32, i32);
struct Point(i32, i32);
let black = Color(0, 0, 0);
let origin = Point(3, 4);
struct Point {
x: i32
y: i32
}
let origin = Point {
x: 3
y: 4
}
3.2 枚举
定义枚举
enum IpAddrKind {
V4,
V6,
}
使用枚举值
let four = IpAddrKind::V4;
fn route(ip_type: IpAddrKind) { }
route(four);
route(IpAddrKind::V6);
枚举成员关联数据
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
更复杂的例子
enum Message {
Quit, // 不关联数据
Move { x: i32, y: i32 }, // 匿名结构体
Write(String),
ChangeColor(i32, i32, i32),
}
match 控制流
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Option
来表示。
Option 的定义如下:
pub enum Option<T> {
Some(T),
None,
}
包含2个枚举项:
1) None,表明失败或没有值
2) Some(value),元组结构体,封装了一个 T 类型的值 value
得益于Option,Rust 不允许一个可能存在空值的值,像一个正常的有效值那样工作,在编译时就能够检查出来。Rust显得更加安全,不用担心出现其他语言运行时才会出现的空指针异常的bug。例如:
let x: i8 = 5; // Rust 没有空值(Null),因此 i8只能被赋予一个有效值。
let y: Option<i8> = Some(5); // y 可能为空,需要显示地表示为枚举类型 Option
let sum = x + y;
相加时,编译器会报错:
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is
not satisfied
-->
|
5 | let sum = x + y;
| ^ no implementation for `i8 + std::option::Option<i8>`
|
,否则必须赋予有效值。而为了使用Option,需要编写处理每个成员的代码,当T 为有效值时,才能够从 Some(T) 中取出 T 的值来使用,如果 T 为无效值,可以进行其他的处理,通常使用 match 来处理这种情况。
例如,当y为有效值时,返回x和y的和;为空值时,返回x。
fn plus(x: i8, y: Option<i8>) -> i8 {
match y {
None => x,
Some(i) => x + i,
}
}
fn main() {
let y1: Option<i8> = Some(5);
let y2: Option<i8> = None;
let z1 = plus(10, y1);
let z2 = plus(10, y2);
println!("z1={}, z2={}", z1, z2); // z1=15, z2=10
}
if let 控制流
fn plus(x: i8, y: Option<i8>) {
match y {
Some(i) => { println!("x + y = {}", x + i) },
None => {},
}
}
fn main() {
let y1: Option<i8> = Some(5);
let y2: Option<i8> = None;
plus(10, y1); // x + y = 15
plus(10, y2);
}
fn plus(x: i8, y: Option<i8>) {
if let Some(i) = y {
println!("x + y = {}", x + i);
}
}
fn plus(x: i8, y: Option<i8>) {
if y.is_some() {
let i = y.unwrap(); // 获得 Some 中的 T 值。
println!("x + y = {}", x + i);
}
}
fn plus(x: i8, y: Option<i8>) {
if let Some(i) = y {
println!("x + y = {}", x + i);
} else {
println!("y is None");
}
}
// 等价于
fn plus(x: i8, y: Option<i8>) {
match y {
Some(i) => { println!("x + y = {}", x + i) },
None => { println!("y is None") },
}
}
3.3 实现方法和接口(impl & trait)
实现方法(impl)
impl 在 struct、enum 或者trait 上实现方法。
关联函数 (associated function) 的第一个参数通常为self参数,有3种变体:
self: 允许实现者移动和修改对象,对应的闭包特性为FnOnce。
&self: 既不允许实现者移动对象也不允许修改,对应的闭包特性为Fn。
&mut self: 允许实现者修改对象但不允许移动,对应的闭包特性为FnMut。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle::square(10);
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
println!(
"The area of the rectangle is {} square pixels.",
rect2.area()
);
}
==, !=, >=),
也不支持像+和*这样的双目运算符,需要自己实现,或者使用match进行匹配。
类似于 lua 里面的 metatables,可以通过实现一些借口函数来实现自定义结构体的运算:
struct User {
name:String,
age:i32,
}
impl Eq for User {
fn eq(&self, other: User) -> bool {
self.age == other.age
}
}
实现接口(trait)
trait Summary {
fn summarize(&self) -> String;
}
impl Summary for Rectangle {
fn summarize(&self) -> String {
format!("{width={}, height={}}", self.width, self.height)
}
}
// 接口也支持继承
trait Person {
fn full_name(&self) -> String;
}
trait Employee : Person { //Employee inherit from person trait
fn job_title(&self) -> String;
}
trait Expat {
fn salary(&self) -> f32
}
trait ExpatEmployee : Employee + Expat { // 多继承,同时继承 Employee 和 Expat
fn additional_tax(&self) -> f64;
}
trait Foo {
fn foo(&self);
// default method
fn bar(&self) { println!("We called bar."); }
}
// inheritance
trait FooBar : Foo {
fn foobar(&self);
}
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
如果一个特性不在当前作用域内,它就不能被实现。
不管是特性还是impl,都只能在当前的包装箱内起作用。
impl Trait
//before
fn foo() -> Box<Trait> {
// ...
}
//after
fn foo() -> impl Trait {
// ...
}
trait Trait {
fn method(&self);
}
impl Trait for i32 {
// implementation goes here
}
impl Trait for f32 {
// implementation goes here
}
//before
fn foo() -> Box<Trait> {
Box::new(5) as Box<Trait>
}
//after
fn foo() -> impl Trait {
5
}
// before
fn foo() -> Box<Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
// after
fn foo() -> impl Fn(i32) -> i32 {
|x| x + 1
}
// before
fn foo<T: Trait>(x: T) {
// after
fn foo(x: impl Trait) {
3.4 泛型(Generics)
告诉编译器 T 是泛型。
函数中使用泛型
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);
}
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
3.5 常见集合 Vec
新建
let v: Vec<i32> = Vec::new(); // 空集合
// let v = vec![1, 2, 3]; // 含初始值的集合,vec!是为方便初始化Vec提供的宏。
println!("第三个元素 {}", &v[2]); // 3
println!("第100个元素 {}", &v[100]); // panic error
assert_eq!(v.get(2), Some(&3));
assert_eq!(v.get(100), None);
更新
let v: Vec<i32> = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
v.pop() //删除最后一个元素
遍历
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
let mut v2 = vec![100, 32, 57];
for i in &mut v2 {
*i += 50;
}
if let 控制流
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
{
let third = v.get_mut(2).unwrap();
*third += 50;
}
println!("v={:?}", v); // v=[1, 2, 53, 4, 5]
}
枚举类型,那么可以使用if let来简化代码。
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
if let Some(third) = v.get_mut(2) {
*third += 50;
}
println!("v={:?}", v); // v=[1, 2, 53, 4, 5]
}
while let 控制流
let mut stack = vec![1, 2, 3, 4, 5];
while let Some(top) = stack.pop() {
println!("{}", top); // 依次打印 5 4 3 2 1
}
let v = vec![1, 2, 3];
for i in &v { .. } // 获得引用
for i in &mut v { .. } // 获得可变引用
for i in v { .. } // 获得所有权,注意此时Vec的属主将会被转移!!
// @question 为什么说使用for容易造成嵌套循环
3.6 常见集合 String
新建
let mut s1 = String::new();
let s2 = "initial contents".to_string();
let s3 = String::new();
更新
let mut s = String::from("foo");
s.push_str("bar"); // 附加字符串
s.push('!') // 附加单字符
assert_eq!(s.remove(0), 'f'); // 删除某个位置的字符
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;
format
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s); // tic-tac-toe
索引
let v = String::from("hello");
assert_eq!(Some('h'), v.chars().nth(0));
遍历
let v = String::from("hello");
for c in v.chars() {
println!("{}", c);
}
的封装,
但是有些字符可能会占用超过2个字符,所以String不支持直接索引,如果需要索引需要使用 chars() 转换后再使用。
3.7 常见集合 HashMap
新建
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
访问
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
更新
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10); // 10
scores.insert(String::from("Blue"), 25); // 25
// Blue 存在则不更新,不存在则更新,因此scores['Blue'] 仍为 25
scores.entry(String::from("Blue")).or_insert(50);
4 错误处理
4.1 不可恢复错误 panic!
直接调用
fn main() {
panic!("crash and burn");
}
$ cargo run
Compiling tutu v0.1.0 (/xxx/demo/tutu)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/tutu`
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
代码bug引起的错误
fn main() {
let v = vec![1, 2, 3];
v[99]; // 越界
}
$ RUST_BACKTRACE=1 cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/tutu`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /rustc/xxx/src/libcore/slice/mod.rs:2717:10
stack backtrace:
0: backtrace::backtrace::libunwind::trace
at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.37/src/backtrace/libunwind.rs:88
...
17: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
at /rustc/xxx/src/liballoc/vec.rs:1796
18: tutu::main
at src/main.rs:4
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
release
[profile.release]
panic = "abort"
4.2 可恢复错误 Result
处理 Result
enum Result<T, E> {
Ok(T),
Err(E),
}
fn main(){
let f: u32 = File::create("hello.txt");
}
= note: expected type `u32`
found type `std::result::Result<std::fs::File, std::io::Error>`
获取到文件句柄。
下面是一个完整的示例,创建 hello.txt 文件,并尝试写入 “Hello, world!”。
use std::fs::File;
use std::io::prelude::*;
fn main() {
let f = File::create("hello.txt");
let mut file = match f {
Ok(file) => file,
Err(error) => {
panic!("Problem create the file: {:?}", error)
},
};
match file.write_all(b"Hello, world!") {
Ok(()) => {},
Err(error) => {
panic!("Failed to write: {:?}", error)
}
};
}
unwrap 和 expect
中的值,如果失败,则直接调用 !panic,程序结束。
let f = File::open("hello.txt").unwrap(); // 若成功,f则被赋值为文件句柄,失败则结束。
let f = File::open("hello.txt").expect("Failed to open hello.txt");
返回 Result
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
use std::io;
use std::io::Read;
use std::fs::File;
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)
}
5 包、crate和模块
5.1 包和 crate
.
├── Cargo.lock
├── Cargo.toml
├── benches
│ └── large-input.rs
├── examples
│ └── simple.rs
├── src
│ ├── bin
│ │ └── another_executable.rs
│ ├── lib.rs
│ └── main.rs
└── tests
└── some-integration-tests.rs
5.2 模块
声明模块
// src/main.rs
mod math {
mod basic {
fn plus(x: i32, y: i32) -> i32 { x + y}
fn mul(x: i32, y: i32) -> i32 { x * y}
}
}
fn main() {
println!("2 + 3 = {}", math::basic::plus(2, 3));
println!("2 * 3 = {}", math::basic::mul(2, 3));
}
引入作用域
// src/lib.rs
pub mod greeting {
pub fn hello(name: &str) { println!("Hello, {}", &name) } // pub 才能外部可见
}
// src/main.rs
use tutu;
fn main() {
tutu::greeting::hello("Jack"); // Hello, Jack
}
// src/main.rs
use tutu::greeting;
fn main() {
greeting::hello("Jack");
}
分隔模块
// src/greeting.rs
pub fn hello(name: &str) { println!("Hello, {}", &name) }
// src/lib.rs
pub mod greeting;
pub fn func() {
greeting::hello("Tom");
}
// src/main.rs
use tutu::greeting;
fn main() {
greeting::hello("Jack");
}
6 测试
6.1 单元测试(unit tests)
fn plus(x: i32, y: i32) -> i32 {
x + y
}
fn main() {
let x = 10;
let y = 20;
println!("{} + {} = {}", x, y, plus(x, y))
}
#[test]
fn it_works() {
assert_eq!(4, plus(2, 2), );
}
$ cargo test
running 1 test
test it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
fn plus(x: i32, y: i32) -> i32 {
x + y
}
fn main() {
println!("2 + 3 = {}", plus(2, 3));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(4, plus(2, 2), );
}
}
6.2 集成测试(integration tests)
├── Cargo.toml
├── src
│ └── lib.rs
│ └── main.rs
├── tests
└── test_lib.rs
// src/lib.rs
pub fn plus(x: i32, y: i32) -> i32 { // plus 必须是公共API,才能被集成测试。
x + y
}
// src/main.rs
use tutu;
fn main() {
println!("2 + 3 = {}", tutu::plus(2, 3));
}
// tests/test_lib.rs
use tutu;
#[test]
fn it_works() {
assert_eq!(4, tutu::plus(2, 2));
}
running 1 test
test it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
7 生命周期
借用不改变内存的所有者(Owner),借用只是对源内存的临时引用。
在借用周期内,借用方可以读写这块内存,所有者被禁止读写内存;且所有者保证在有“借用”存在的情况下,不会释放或转移内存。
失去所有权的变量不可以被借用(访问)。
在租借期内,内存所有者保证不会释放/转移/可变租借这块内存,但如果是在非可变租借的情况下,所有者是允许继续非可变租借出去的。
借用周期满后,所有者收回读写权限
借用周期小于被借用者(所有者)的生命周期。