【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十、泛型(Generic Type)、特征(Trait)和生命周期(Lifetimes)
每一门编程语言都有有效处理重复的工具。我们可以使用泛型来定义具体类型或其他属性的抽象替代品。
主要教材参考 《The Rust Programming Language》
范例:求最大值
fn max(list: &[i32]) ->i32{
let mut largest = list[0];
// for &item in list {
// if item> largest {
// largest = item;
// }
// }
for item in list {
if *item> largest {
largest = *item;
}
}
largest
}
fn main() {
let result = max(&vec![34,50,25,100,65]);
println!("{:?}", result);
let result = max(&vec![102,34,6000,189,54]);
println!("{:?}", result);
}
需要注意的是:注释部分的for 循环和下列循环的作用是相同的,*
的作用表示解引用。
函数中的泛型主要用于定义参数和返回值的类型。
fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
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);
}
此时编译会有如下的编译错误
my-project % cargo run
Compiling my-project v0.1.0 (~/Desktop/code/rust_code/my-project)
error[E0369]: binary operation `>` cannot be applied to type `&T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- &T
| |
| &T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `my-project` (bin "my-project") due to previous error
错误提示说,对于 T 可能是的所有的类型,该方法并不适用,为了能够是使用>比较,笔试在类型上实现
std::cmp::PartialOrd
这个 接口。
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
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());
}
使用泛型的代码和使用具体类型的代码运行速度是一样的。大致来说编译器实现泛型的两种方式
装箱的思路就是将所有类型包装成统一的类型,有了统一的类型就有了统一的内存模型,这样函数在调用时传递的就是统一的数据类型,也就不会出现类型不匹配的问题。
Rust 在编译的时候执行单态化(Monomorphization),单态化的思路就是自动生成多个类型的泛型函数版本,看起来就是一个模板代码的生成的过哦差,但是也需要考虑很多种情况。
Trait 告诉 Rust 编译器某种类型具有哪些并且可以与其他类型共享的功能。Trait 是抽象的定义共享行为。Trait bounds (约束):泛型类型参数指定了为实现了特定行为的类型。在别的编程语言可以看作接口,但是有些区别。
Trait 的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为。Trait 使用关键字 trait 来定义,只有方法的定义没有具体实现,一个trait 可以有多个方法,每个方法占一行以;
结尾。
pub trait Summary{
fn summarize(&self) -> String;
}
pub trait Summary{
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub trait Summary{
fn summarize(&self) -> String;
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
notify(&tweet);
}
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
use std::fmt::Display;
pub fn notify01(item: impl Summary + Display) {
println!("Breaking news! {}", item.summarize());
}
pub fn notify02<T: Summary + Display>(item: T) {
println!("Breaking news! {}", item.summarize());
}
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
生命周期的主要目标就是:避免悬垂引用(dangling reference)
Rust 编译器的借用检查器:比较作用域来判断所有的借用是否合法。
我们来看下面代码。
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
}
fn longest(x: &str, y: &str) -> &str{
if x.len() > y.len() {
x
}else {
y
}
}
上述代码将会报错
raojiamin@192 my-project % cargo run
Compiling my-project v0.1.0 (/Users/raojiamin/Desktop/code/rust_code/my-project)
error[E0106]: missing lifetime specifier
--> src/main.rs:8:33
|
8 | fn longest(x: &str, y: &str) -> &str{
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
8 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str{
| ++++ ++ ++ ++
For more information about this error, try `rustc --explain E0106`.
error: could not compile `my-project` (bin "my-project") due to previous error`
根据错误提示可以看出借用检查器不知道返回值类型包含借用的值是使用哪个生命周期。
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("result is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str{
if x.len() > y.len() {
x
}else {
y
}
}
语法:
'
)开头,通常全小写且非常短,很多人使用'a
&i32 // 一个引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
单个生命周期标注本身没有意义。
泛型生命周期参数声明在:函数和参数列表之间的<>
里面
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str;
生命周期取决于的参数函数最短的一个生命周期。
这就是悬垂引用,该值在函数结束时候就走出了作用域;
struct 里面可以包含自持有的类型、引用(需要在每个引用上添加生命周期标注)
早期 rust 编译器要求每个引用都需要添加生命周期标注。
在 Rust 引用分析中所需要编入的模式称为生命周期省略规则
生命周期省略规则不会提供完整的推断,如果应用规则后,引用的生命周期仍然模糊不清,则出现编译错误。解决办法就是添加生命周期标注,表明引用间的相互关系;
编译器使用3个规则在没有显式标注生命周期的情况下,来确定引用的生命周期
规则 1: 每个引用类型的参数都有自己的生命周期
规则 2: 如果只有 1 个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数
规则 3: 如果有多个输入生命周期参数,但其中一个是 &self 或 &mut self (方法),那么 self 的生命周期会被赋给所有的输出生命周期参数
'static
是一个特殊的生命周期:整个程序的持续时间。例如:所有的字符串字面知值都拥有 'static
生命周期。
为引用指定 'static
生命周期前要三思,是否需要引用在程序整个生命周期内都存活。
以上就是今天要讲的内容