Obeta

rust简单入门

学习rust过程中的部分笔记,最近也没什么写的,正好可以把这个放出来.

编译 rust 程序

在 Linux 或 OSX 上,输入如下命令:

rustc main.rs
./main
Hello, world! # 输出

在 Windows 下,使用 main.exe 而不是 main

可以使用 Cargo 构建:

cargo build # 输出
   Compiling hello_world v0.0.1 (file:///home/yourname/projects/hello_world)
./target/debug/hello_world # 输出
Hello, world! # 输出

然后运行:

cargo run
     Running `target/debug/hello_world` # 输出
Hello, world! # 输出

run命令包含了build,如果文件没有改动,将直接run

发布构建

cargo build --release

创建新Cargo项目

Cargo 来开始一个新项目,在命令行输入cargo new

cargo new hello_world --bin

这个命令传递了--bin 参数因为我们的目标是直接创建一个可执行程序,而不是一个库。可执行文件通常叫做二进制文件(因为它们位于/usr/bin,如果你使用 Unix 系统的话)

数组

let mut names = ["zhou", "yue", "xie", "he", "jing"];
// or
let number = [0; 20]; // 生成20个0, [i32; 20]

println!("names has {} elements", names.len());
println!("The second name is: {}", names[1]);

切片

你可以用一个&[]的组合从多种数据类型创建一个切片.&表明切片类似于引用.带有一个范围的[],允许你定义切片的长度.

let a = [0, 1, 2, 3, 4];
let complete = &a[..]; // 一个完整的切片引用.
let middle = &a[1..4]; // 从索引1开始,至索引4之前的元素.

字符串也是一个切片:

let s = "Hello, world!";
let my_string = String::from("hello world");
fn first_word(s: &str) -> &str {
	s
}
first_word(s);
first_word(&s[..]); // 也可以复制一个切片
first_word(&my_string[..]);// 使用String类型

s这里的类型是&str:它是指向二进制特定点的切片,这也是字符串文字不可变的原因, &str是一个不可变的引用。

元组

元组(tuples)是固定大小的有序列表.

let x = (1, "hello");
let y: (i32, &str) = (1, "hello"); // 注明了数据类型

// 元组互相赋值(前提包含相同的类型和数量)
let mut x = (1, 2); // x: (i32, i32)
let y = (2, 3); // y: (i32, i32)
x = y;

// 结构let
let (x, y, z) = (1, 2, 3);
println!("x is {}", x);

// 通过索引访问元组元素
let tuple = (1, 2, 3);
let x = tuple.0; // 使用.而不是[]
let y = tuple.1;
println!("x is {}", x);

函数

// 定义foo,接受一个32位有符号整形值x,并返回32位有符号整形x
fn foo(x: i32) -> i32 {
  x
}

// x是一个“函数指针”,指向一个获取一个i32参数并返回一个i32值的函数foo
let x: fn(i32) -> i32 = foo;

if 语句

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 :(");
}

或者这样写:

let x = 5;

// 1.正常写
let y = if x == 5 {
  10
} else {
  15
}; // y: i32

// 2. 简写(推荐)
let y = if x == 5 { 10 } else { 15 }; // y: i32

循环

包含loop,while,for三个循环.

loop: 无限循环.

loop {
  println!("Loop forever!");
}

while:当你不确定应该循环多少次时正确的选择.

let mut x = 5; // mut x: i32
let mut done = false; // mut done: bool

while !done {
  x += x - 3;

  println!("{}", x);

  if x % 5 == 0 {
    done = true;
  }
}

for:循环一个特定的次数.

// 注意这个`0..10`不包含上限值10,也就是0到9
for x in 0..10 {
  println!("{}", x); // x: i32
}

// 也可以反顺序
for x in (0..10).rev() {
	println!("{}", x);
}

// 迭代器
for var in expression {
  code
}

// 需要知道index
for (index, value) in (5..10).enumerate() {
  println!("index = {} and value = {}", index, value);
}

breakcontinue在三个循环中都有效,作用跟 JavaScript 中的作用是一样的.也可以像下面这样使用:

'outer: for x in 0..10 {
  'inner: for y in 0..10 {
    if x % 2 == 0 { continue 'outer; } // Continues the loop over `x`.
    if y % 2 == 0 { continue 'inner; } // Continues the loop over `y`.
    println!("x: {}, y: {}", x, y);
    }
}

当你有嵌套的循环而希望指定你的哪一个breakcontinue该起作用。就像大多数语言,默认breakcontinue将会作用于最内层的循环。当你想要一个breakcontinue作用于一个外层循环,你可以使用标签来指定你的breakcontinue语句作用的循环。如上的代码只会在 x 和 y 都为奇数时打印他们.

Vectors

“Vector”是一个动态或“可增长”的数组,被实现为标准库类型 Vec(其中是一个泛型语句)。vector 总是在堆上分配数据。vector 与切片就像 String 与&str 一样。你可以使用 vec!宏来创建它.

let v = vec![1, 2, 3, 4, 5]; // v: Vec<i32>
// OR
let v = vec![0; 10]; // ten zeroes

// 访问
println!("The third element of v is {}", v[2]);

// 另外值得注意的是必须用usize类型的值来索引
let v = vec![1, 2, 3, 4, 5];
let i: usize = 0;
let j: i32 = 0;

v[i]; // Works:
v[j]; // Doesn’t:

// 迭代
let mut v = vec![1, 2, 3, 4, 5];

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);
}

struct

结构类似于元组,与元组一样,结构的各个部分可以是不同的类型。与元组不同,您可以为每个数据命名,以便清楚这些值的含义。由于这些名称,结构比元组更 ​​ 灵活:您不必依赖数据的顺序来指定或访问实例的值。

struct User {
	username: String,
	email: String,
	sign_in_count: u64,
	active: bool,
};
struct Color(i32, i32, i32);

let user1 = User {
	email: String::from("someone@example.com"),
	username: String::from("someusername123"),
	active: true,
	sign_in_count: 1,
};
println!("{}", user1.email);

let user2 = User {
	email: String::from("another@example.com"),
	username: String::from("anotherusername567"),
	..user1 // struct update语法,跟JavaScript中展开符类似
};
println!("{}", user2.email);

let black = Color(0, 0, 0);
println!("{}", black.2);

我们可以给struct增加一些方法,就像给 JavaScript 的对象增加方法一样:

struct Rectangle {
	width: u32,
	height: u32,
}

impl Rectangle {
	fn area(&self) -> u32 {
		self.width * self.height
	}
	// 这是一个关联函数:创建一个新的正方形实例
	fn square(size: u32) -> Rectangle {
		Rectangle { width: size, height: size }
	}
}

// 多个impl可以分开写
impl Rectangle {
	fn can_hold(&self, other: &Rectangle) -> bool {
		self.width > other.width && self.height > other.height
	}
}

let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 20, height: 40 };
let square = Rectangle::square(4);

println!(
	"The area of the rectangle is {} square pixels.",
	rect1.area()
);
println!(
	"Square {}",
	square.area()
);
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));

可以看到area方法第一个参数是self,跟Python挺像,如果没有传递self,那就是一个关联函数,相当于类里的一些静态方法.

枚举

enum Color {
	rgb,
	hex,
	hsl,
	cmyk,
	hsv
};
struct ColorValue {
	type: Color,
	value: String
};

let type1 = Color::rgb;
let type1 = COlor::hsl;

// 类型与值分开放
let rgbVal = ColorValue {
	type: type1,
	value: String::from("rgb(0,0,0)")
};

或者说我们可以放到一起,这样就不需要额外的结构:

enum Color {
	rgb(String),
	hex(String),
	hsl(String),
	cmyk(String),
	hsv(String)
};

let type1 = Color::rgb(String::from("rgb(0,0,0)"));

// 如果需要获取枚举中的值,那么需要使用match或者if let来解构
match type1 {
	Color::rgb(value) => println!("value is {}", value),
	_ => pringln!("noting"),
}
// 或者
if let Color::rgb(value) = type1 {
	println!("value is {}", value);
}

这里来看一个复杂一点的例子:

enum Message {
	Quit,
	Move { x: i32, y: i32 },
	Write(String),
	ChangeColor(i32, i32, i32),
}
  • Quit根本没有与之相关的数据。
  • Move在其中包含一个匿名结构。
  • Write包括一个String
  • ChangeColor包括三个i32值。

还可以像struct一样定义方法:

impl Message {
	fn call(&self) {
		// method body would be defined here
	}
}

let m = Message::Write(String::from("hello"));
m.call();

说完枚举,那么一定要说一下Option,这是 rust 标准库中定义的另一个枚举,它的出现只是为了解决null的问题,常见的语言比如 JavaScript 中空对象是null,而 Python 是None...这些都是类似的.rust 为了内存安全,因此并没有设计具有其他语言类似的null值,但是又不能抛弃这种概率,因为这平时非常有用,因此 rust 更换来实现方式:使用枚举替代空值,可以编码存在与不存在的情况,也就是Option<T>.

enum Option<T> {
	Some(T), // T代表的是一种范型(任何数据结构)
	None,
}

使用过程中我们甚至不需要Option::开头,直接使用Some:

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

match some_number {
	Some(value) => println!("{}", value),
	_ => println!("noting") // 5
}
match some_string {
	Some(value) => println!("{}", value),
	_ => println!("noting") // a string
}
match absent_number {
	Some(value) => println!("{}", value),
	_ => println!("noting") // noting
}

这种方式的引入相当于强制你使用值与获取值之前检查值是否是安全可用的,防止产生令程序奔溃的致命错误.

match

match 在 rust 中非常的有用,可以看成是一种解构:

enum Coin {
	Penny,
	Nickel,
	Dime,
	Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
	match coin {
		Coin::Penny => 1,
		Coin::Nickel => 5,
		Coin::Dime => 10,
		Coin::Quarter => 25,
		_ => {
			println!("noting");
			0
		}
	}
}

也可以配合枚举解构出来其中的值:

let some_number = Some(5);

match some_number {
	Some(value) => println!("{}", value),
	_ => println!("noting") // 5
}

还可以匹配常用的字符串:

// 1
match stringthing.as_ref() {
  "a" => println!("0"),
  "b" => println!("1"),
  "c" => println!("2"),
  _ => println!("something else!"),
}
// 2
match &stringthing[..] {
  "a" => println!("0"),
  "b" => println!("1"),
  "c" => println!("2"),
  _ => println!("something else!"),
}
// 3
match stringthing.as_str() {
  "a" => println!("0"),
  "b" => println!("1"),
  "c" => println!("2"),
  _ => println!("something else!"),
}
// 4
match &stringthing as &str {
  "a" => println!("0"),
  "b" => println!("1"),
  "c" => println!("2"),
  _ => println!("something else!"),
}

Vector

大多数的其它语言有另外一个名称:Array.值得注意的是Vector在定义的时候就需要明确一种类型,不允许存储这种类型之外的:

// 1.定义
let mut v: Vec<i32> = Vec::new();
let mut v1 = vec![1, 2, 3]; // 使用vec!

// 2.更新
v.push(1);
v1.push(4);

// 3.读取
let third: &i32 = &v[2]; // 可能会超出边界
let third1: Option<&i32> = v1.get(2); // 推荐使用

// 4.所有权相关
let first = &v[0];
// v.push(6); // 这一步会报错(回想一下规则,您不能在同一范围内拥有可变和不可变引用)

// 5.删除最后一个元素
assert_eq!(v.pop(), Some(1));
assert_eq!(v1.pop(), Some(4));

在向量的末尾添加一个新元素可能需要分配新内存并将旧元素复制到新空间,如果没有足够的空间将所有元素放在每个元素旁边当前 Vector 的其他位置。在这种情况下,对第一个元素的引用将指向释放的内存。

let mut v = vec![100, 32, 57];
for i in &v {
	println!("{}", i);
}
for i in &mut v {
  *i += 50;
}

上面说了Vector只能存储相同类型的值,但是存储不同类型的值是肯定需要的,rust 通过存储枚举类型来实现这种功能,至于为什么会不能直接存储不同类型的值,官方文档说了着有两个因素:

  • Rust 需要知道在编译时向量中将包含哪些类型,因此它确切地知道存储每个元素需要多少内存.
  • 可以明确说明此向量中允许的类型,使用match表达式可以保证处理任何可能的情况.
enum SpreadsheetCell {
	Int(i32),
	Float(f64),
	Text(String),
}

let row = vec![
	SpreadsheetCell::Int(3),
	SpreadsheetCell::Text(String::from("blue")),
	SpreadsheetCell::Float(10.12),
];

打印

打印功能在任何一门语言中使用都是非常频繁的,比如日志,调试等都需要,因此花点时间介绍一下.

打印基础类型的时候并没有任何问题,但是对于一些其它类型,比如:

struct Rectangle {
	width: u32,
	height: u32,
}

let rect1 = Rectangle { width: 30, height: 50 };

println!("rect1 is {}", rect1); // error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied

println!宏可以接收多种格式,默认情况下,{}大括号告诉println!使用被称为格式Display:用于直接最终用户消费的输出。到目前为止我们看到的基本类型已经有了Display的默认实现。但是对于结构体,println!格式化输出的方式不太清楚,因为结构体有更多的显示可能性:你想要逗号吗?你想打印花括号吗?是否应显示所有字段?由于这种模糊性,Rust 不会试图猜测我们想要什么,并且结构没有提供的实现Display,因此这时候 Rust 就报错了.这时候可以这样:

#[derive(Debug)]
struct Rectangle {
	width: u32,
	height: u32,
}

let rect1 = Rectangle { width: 30, height: 50 };

println!("rect1 is {:?}", rect1); // rect1 is Rectangle { width: 30, height: 50 }
println!("rect1 is {:#?}", rect1);
// rect1 is Rectangle {
// 	width: 30,
// 	height: 50
// }

我们使用了#[derive(Debug)]且将说明符:?放在大括号内会告诉println!我们要使用一个名为的输出格式Debug。该Debug特性使我们能够以对开发人员有用的方式打印我们的结构,这样我们就可以在调试代码时看到它的值.

所有权

Rust 中的变量绑定有一个属性:它们有它们所绑定的的值的所有权。这意味着当一个绑定离开作用域,它们绑定的资源就会被释放。例如:

fn foo() {
  let v = vec![1, 2, 3];
}

当 v 进入作用域,一个新的 vector 在栈上被创建,并在堆上为它的 3 个元素分配了空间。当 v 在 foo()的末尾离开作用域,Rust 将会清理掉与向量(vector)相关的一切,甚至是堆上分配的内存。这在作用域的结尾是一定(deterministically)会发生的。

请闹记所有权的规则:

  • Rust 中的每个值都有一个称为其所有者的变量。
  • 一次只能有一个所有者。
  • 当所有者超出范围时,该值将被删除。

因此这会导致一个挺麻烦的问题:我们不得不在每个我们写的函数中交还所有权

fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
  // Do stuff with `v1` and `v2`.

  // Hand back ownership, and the result of our function.
  (v1, v2, 42)
}

let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let (v1, v2, answer) = foo(v1, v2); // 必须返回v1,v2
println!("{}", v1[1]); // 否则这里无法访问到v1,v2

幸运的是有个办法可以消除这个.我们可以使用借用这个特点,就像其他编程语言一样.

// 借用很像c语言中的引用
fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
  // Do stuff with `v1` and `v2`.

  //  Return the answer.
  42
}

let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let answer = foo(&v1, &v2);

// We can use `v1` and `v2` here!

我们只要在函数定义参数那里加上&代表是引用, 借用了所有权, 一个借用变量的绑定在它离开作用域时并不释放资源.

引用是不可变的,就像绑定一样。这意味着在 foo()中,变量完全不能被改变.

但是有时候我们也需要改变这些变量,那么需要使用&mut,一个“可变引用”允许你改变你借用的资源:

let mut x = 5; // 也需要标志mut
{
  let y = &mut x;
  *y += 1; // y是一个&mut引用,你也需要使用*来访问引用的内容
}
println!("{}", x);

任何借用必须位于比拥有者更小的作用域,这也是为什么要加大括号了.

即同一个作用域下,要么只有一个对资源 A 的可变引用(&mut T),要么有 N 个不可变引用(&T),但不能同时存在可变和不可变的引用.

这是为了内存安全,否则同时包含一个对资源 A 的可变引用 B 与一个对 A 的不可变引用 C 会发生非常危险的事情,比如当 B 改变了 A 的值,C 并不知道,这时候 C 照常使用就会可能导致程序异常并且很难调试.

移动语义(Move semantics)

Rust 确保了对于任何给定的资源都正好(只)有一个绑定与之对应。例如,如果我们有一个 vector,我们可以把它赋予另外一个绑定:

let v = vec![1, 2, 3];
let v2 = v;
// 不过,如果之后我们尝试使用v,我们得到一个错误
println!("v[0] is: {}", v[0]); // error: use of moved value: `v`

当我们把所有权转移给别的绑定变量时,我们说我们“移动”了我们引用的值。这里你并不需要什么类型的特殊注解,这是 Rust 的默认行为.

那么为什么呢? 在移动了绑定后我们不能使用它的原因是微妙的,也是重要的。当我们写了这样的代码:

let v = vec![1, 2, 3]; // 分配了堆内存和栈内存分别给变量v和数据[1,2,3]

let mut v2 = v; // 此时按位拷贝了v在栈上的分配指针内容到v2(浅拷贝),并没有复制一份实际数据

这时候是违反 Rust 的安全保证的, vvec![1, 2, 3] 的这两部分(在堆上的和在栈上的)在任何时候都必须同步像大小,容量这样的信息,而浅拷贝后就无法做到这些,会导致一些无法预期的问题.

let v = vec![1, 2, 3];
let mut v2 = v;
v2.truncate(2);

这时候 v 仍是可以访问的话,这会产生一个无效的 vector,因为它并不知道堆上的数据已经被缩短了。现在 v 在栈上的部分与堆上的相应部分的信息并不一致。v 仍然认为有 3 个元素并乐意我们访问那个并不存在的元素 v[2],不过你可能已经知道这是一个导致灾难的原因。更糟的是会允许未经授权的用户读取他没有访问权限的数据, 这就是为何 Rust 在我们移动后禁止使用 v 的原因.

需要知道的是->基本类型不受限制.

Copy 类型

let v = 1;
let v2 = v;

println!("v is: {}", v);

在这个情况,v 是一个 i32 类型,它实现了Copytrait。这意味着当我们把 v 赋值给 v2,产生了一个数据的拷贝。不过不像一个移动,我们仍可以在之后使用 v。这是因为 i32 并没有指向其它数据的指针,对它的拷贝是一个完整的拷贝.如果类型具有 Copy 特征,则在分配后仍可使用较旧的变量.以下是一些Copy类型:

  • 所有整数类型,例如 u32。
  • 布尔类型,bool 带值 true 和 false。
  • 所有浮点类型,如 f64。
  • 字符类型,char。
  • 元组,但只有它们包含的类型也是如此 Copy。例如, (i32, i32)是 Copy,但(i32, String)不是。

更深层次的原因是在编译时具有已知大小的整数等类型完全存储在堆栈中,因此可以快速复制实际值。这意味着我们没有理由使用 v 在创建变量 v2 后阻止其有效。换句话说,这里的深度和浅度复制没有区别.

let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

如果我们确实想要深度复制堆栈数据 String,而不仅仅是堆栈数据,我们可以使用一个常用的方法来调用clone

错误处理

rust 中包含两种错误处理:

  • panic!:不可恢复,也就是说这是一个终止程序运行的错误报错.
  • Result<T, E>:可恢复,枚举类型错误处理,用于一般的错误提醒.
// panic!
panic!("will break server.");

// Result
enum Result<T, E> {
	Ok(T),
	Err(E),
}

对于panic!没什么好说的,如果需要捕获这类错误,可以使用如下方式:

use std::panic;
panic::set_hook(Box::new(|_info| {
	println!("catch error args")
})); // 设置hook补货,也可以不设置

let result = panic::catch_unwind(|| {
	panic!("break here.");
});

重点是Result<T, E> 类型,大多数时候需要使用这个,会更友好的提示程序的输入问题在哪里,不会动不动就报错.

use std::fs::File;

let f = File::open("hello.txt");

let f = match f {
	Ok(file) => file,
	Err(error) => {
		panic!("There was a problem opening the file: {:?}", error)
	},
};

File::open返回的就是一个Result<T, E>枚举类型,大多数库都是以这种方式返回值,标准库也不例外.这种方式与Option枚举差不多,但是针对的都是不同领域,Option更像是数据处理过程中返回值,而Result<T, E>是一种处理是否异常.

看看如何编写一个返回Result<T, E>的函数:

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), // 如果错误就直接return
	};

	let mut s = String::new();

	// 读取打开的文件内容
	match f.read_to_string(&mut s) {
		Ok(_) => Ok(s),
		Err(e) => Err(e),
	}
}

要是每次都那么写,感觉是非常麻烦的,rust 提供了一种快速的方式来获取值:?操作符.

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)
}

?操作符会将返回的Result进行解构:

  • 返回的是Ok,那么会直接赋值给f
  • 返回的是Err那么直接 return 这个Err.

这样就省去大量的代码,其实还可以更加简练,就像链式调用:

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
	let mut s = String::new();

	File::open("hello.txt")?.read_to_string(&mut s)?;

	Ok(s)
}

但是使用?有一个限制,也就是只能在是返回Result的函数中使用,比如下面这个就不能使用:

use std::io;
use std::io::Read;
use std::fs::File;

fn main() {
	let f = File::open("hello.txt")?;
}

这是因为?操作符对于Err类型是直接返回,因此对于整个函数的要求是返回的是Result,否则无法编译.

还可以使用unwrap,expect来获取Ok(value)中返回的value值.

fn test(number: usize) -> Result<usize, String> {
	if number > 10 {
		Ok(number)
	} else {
		Err(String::from("number is not alright"))
	}
}

println!("{}", test(11).unwrap()); // 11
println!("{}", test(11).expect("the number is break")); // 11

unwrapexpect的区别就在于expect会有报错提示信息,能让你更方便的定位错误位置,因为你可能使用的是别人提供的库,当库报错的时候你不清楚到底哪个位置出了问题.

生命周期

rust 中有非常多关于生命周期的概念.

函数或方法参数的生命周期称为输入生命周期,返回值的生命周期称为输出生命周期。编译器使用三个规则来确定没有显式注释时生命周期引用的内容。第一条规则适用于输入生命周期,第二条和第三条规则适用于输出生命周期。如果编译器到达三个规则的末尾并且仍然存在无法计算生命周期的引用,则编译器将停止并出现错误。

  1. 作为引用的每个参数都有自己的生命周期参数。换句话说,一个参数的函数获得一个生命周期参数:fn foo<'a>(x: &'a i32); 带有两个参数的函数获得两个单独的生命周期参数:fn foo<'a, 'b>(x: &'a i32, y: &'b i32).
  2. 如果只有一个输入生命周期参数,则将该生命周期分配给所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32.
  3. 如果有多个输入生命周期参数,其中第一个参数是&self&mut self(说明这是一个方法),则将生命周期self分配给所有输出生命周期参数。第三个规则使得方法读取和写入更好,因为需要更少的符号。

有些时候编写到函数不需要显式声明这些生命周期参数,这是因为 Rust 团队发现在一些特定到情况下程序员反复到输入相同的生命周期函数是非常降低开发效率的,因此收集了这类情况后使编辑器更加智能补充,因此在规则 1 的情况下推断出来了生命周期.下面通过一个例子来了解如何使用规则来推断是否需要显式声明这些生命周期参数:

// 基本声明
fn first_word(s: &str) -> &str {}

// 1.第一条规则,每个参数获得自己的生命周期
fn first_word<'a>(s: &'a str) -> &str {}

// 2.第二条规则,只有一个输入生命周期,因此将这个生存期分配给所有输出参数
fn first_word<'a>(s: &'a str) -> &'a str {}

// 3,不符合

结束后发现函数签名中的所有引用都具有生命周期了,说明编译器可以推断出来各个的生命周期,而无需我们在函数签名中注释生命周期,因此基本声明是可以通过变编译的.

来看看另一个不能通过的例子:

// 基本声明
fn longest(x: &str, y: &str) -> &str {}

// 1,符合
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {}

// 2, 不符合
// 3, 不符合

到第三条结束的时候发现返回值声明并没有合适的生命周期对应(编译器无法推断出来),这时候就需要我们显式去注释生命周期了,下面就是我们自己补全后的声明.

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {}

而对于一些方法来说,就需要用到第三条规则:

struct Color<'a> {
	name: &'a str
}
impl<'a> Color<'a> {
	fn get(&self) -> &str {
		self.name
	}
}
let name = "zhouyuexie";
let color1 = Color {
	name
};
println!("{}", color1.get());

相当于:

impl<'a> Color<'a> {
	fn get(&'a self) -> &'a str {
		self.name
	}
}

还有一种是静态声明周期,只针对于字符串:

let s: &'static str = "I have a static lifetime.";

该字符串的文本直接存储在程序的二进制文件中,该二进制文件始终可用。因此,所有字符串文字的生命周期是'static

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.