本文主要是介绍13.泛型、trait和生命周期(下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 6. 生命周期与引用有效性
- 6.1 生命周期引入
- 6.2 得到长度最大的String值
- 6.3 生命周期标注语法
- 1)说明
- 2)普通标注示例
- 3)函数参数中的生命周期标注
- 6.4 深入理解生命周期
- 6.5 结构体定义中的生命周期标注
- 6.6 生命周期省略
- 6.7 方法定义中的生命周期标注
- 6.8 静态生命周期
- 6.9 结合泛型类型参数、trait bounds 和生命周期
6. 生命周期与引用有效性
- Rust 中的每一个引用都有其生命周期(lifetime);
- 大部分时候生命周期是隐含并可以推断的;
- 当出现引用的生命周期以一些不同方式相关联的情况,需要使用泛型生命周期参数来注明他们的关系;
6.1 生命周期引入
fn main() {let r;{let x = 5;r = &x;}println!("r: {}", r);
}
这段代码报错
- x 在到达第 7 行内部作用域结束时就离开了作用域,r的引用值也就不存在了;
6.2 得到长度最大的String值
下面的代码会得到传入两个参数中最长的String,为了避免所有权的占用,代码采用引入的方法传参
fn longest(src1: &str, src2: &str) -> &str {if src1.len() > src2.len() {src1}else{src2}
}fn main() {let string1 = String::from("abcd");let string2 = "xyz";let result = longest(string1.as_str(), string2);println!("The longest string is {}", result);
}
- 然而,编译时提示存在生命周期的错误
6.3 生命周期标注语法
1)说明
- 生命周期标注并不改变任何引用的生命周期的长短;
- 指定了泛型生命周期后函数也能接受任何生命周期的引用;
- 生命周期标注描述了多个引用生命周期相互的关系,而不影响其生命周期;
- 生命周期参数名称必须以撇号(')开头,其名称通常全是小写,
'a
是大多数人默认使用的名称; - 生命周期参数标注位于引用的 & 之后,并有一个空格来将引用类型与生命周期标注分隔开;
2)普通标注示例
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
3)函数参数中的生命周期标注
fn longest<'a>(src1: &'a str, src2: &'a str) -> &'a str {if src1.len() > src2.len() {src1}else{src2}
}
- 生命周期参数需要声明在函数名和参数列表之间的尖括号中;
- 这里说明关于参数中的引用和返回值之间的限制是他们都必须拥有相同的生命周期;
- 生命周期标记仅仅出现在函数的参数中,不存在于函数体中的任何代码中;
- 泛型生命周期’a的具体生命周期等同于src1 和src2的生命周期中较小的那一个;
传递拥有不同具体生命周期的引用来限制longest函数的使用。
fn main() {let string1 = String::from("long string is long");{let string2 = String::from("xyz");let result = longest(string1.as_str(), string2.as_str());println!("The longest string is {}", result); //The longest string is long string is long}
}
- string1直到main函数结束之前都是有效的;
- string2只在最里面的{}内才有效,而result则引用了直到内部作用域结束都是有效的值;
更改代码再尝试
fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("xyz");result = longest(string1.as_str(), string2.as_str());}println!("The longest string is {}", result);
}
无法通过编译
- 第18行的result变量是有效的;
- longest函数的参数和返回值使用了相同的生命周期参数;
- 因此,string2需要直到main函数结束都前都是有效的;
6.4 深入理解生命周期
指定生命周期参数的正确方式依赖函数实现的具体功能;
fn longest<'a>(x: &'a str, y: &str) -> &'a str {x
}
- longest总是返回第一个参数,因此不需要为参数y指定生命周期;
从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配
fn longest<'a>(x: &str, y: &str) -> &'a str {let result = String::from("really long string");result.as_str()
}
编译报错
- 返回的引用没有指向任何一个参数;
- 因此它指向的是一个内部创建的值,这就会造成悬垂引用,因此无法编译通过;
6.5 结构体定义中的生命周期标注
- 可以定义包含引用的结构体;
- 需要为结构体定义中的每一个引用添加第一期周期标注;
struct ImportantExcerpt<'a> {part: &'a str,
}fn main() {let novel = String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");let i = ImportantExcerpt { part: first_sentence };
}
- ImportantExcerpt结构体中存放了一个字符串切片类型(&str ),因此需要声明生命周期;
- 在结构体名称后面的尖括号中声明泛型生命周期参数;
- main函数创建了一个ImportantExcerpt实例,它存放了变量novel的引用;
- novel在ImportantExcerpt之前已经存在,且在ImportantExcerpt离开作用域之后novel都不会离开作用域;
- 因此能通过编译;
6.6 生命周期省略
- 每一个引用都有一个生命周期;
- 需要为那些使用了引用的函数或结构体指定生命周期;
然而,下面的代码能通过编译器检查
fn first_word(s: &str) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i];}}&s[..]
}
- 函数定义了一个参数和返回值都是引用却没有使用生命周期标注的函数;
- 这是一种生命周期省略规则,省略规则只需要提示出错的时候补全生命周期标注即可;
- 规则适用于fn定义以及fmpl块;
引用不需要标注生命周期的规则
- 函数或方法的参数的生命周期被称为输入生命周期;
- 返回值的生命周期被称为输出生命周期;
适用于输入生命周期的规则
规则一:每一个是引用的参数都有它自己的生命周期参数。
例如:fn foo<'a>(x: &'a i32){} fn foo<'a, 'b>(x: &'a i32, y: &'b i32){}
因此
fn first_word(s: &str) -> &str{}
被当作
fn first_word<'a>(s: &'a str) -> &str{}
适用于输出生命周期的规则
规则一:如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
例如
fn foo<'a>(x: &'a i32) -> &'a i32
因此
fn first_word(s: &str) -> &str{}
被当作
fn first_word<'a>(s: &'a str) -> &'a str {
规则二:如果方法有多个输入生命周期参数并且其中一个参数是&self或&mut self,说明是个对象的方法, 那么所有输出生命周期参数被赋予 self 的生命周期
6.7 方法定义中的生命周期标注
- 为带有生命周期的结构体实现方法时,其语法依然类似泛型类型参数的语法;
- 声明和使用生命周期参数的位置依赖于生命周期参数是否同结构体字段或方法参数和返回值相关;
- 实现方法时,结构体字段的生命周期必须总是在 impl 关键字之后声明并在结构体名称之后被使用;
- impl 块里的方法签名中,引用可能与结构体字段中的引用相关联,也可能是独立的
struct ImportantExcerpt<'a> {part: &'a str,
}impl<'a> ImportantExcerpt<'a> {fn level(&self) -> i32 {3}
}
- 为ImportantExcerpt实现了方法level;
- 方法level唯一的参数是self引用;
- impl和类型名称之后的生命周期参数是必须要有的;
- 不必须标注 self 引用的生命周期;
适用于输出生命周期第二条规则的例子
impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part(&self, announcement: &str) -> &str {println!("Attention please: {}", announcement);self.part}
}
- 有两个输入生命周期,Rust应用输入生命周期的规则给予 &self 和 announcement 他们各自的生命周期;
- 由于其中一个参数是&self,返回值类型被赋予了&self 的生命周期;
6.8 静态生命周期
'static
的生命周期能够存活于整个程序期间;- 所有的字符串字面量都拥有
'static
生命周期; - 也可以显式标注;
- 字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的;
let s: &'static str = "I have a static lifetime.";
6.9 结合泛型类型参数、trait bounds 和生命周期
use std::fmt::Display;fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a strwhere T: Display
{println!("Announcement! {}", ann);if x.len() > y.len() {x} else {y}
}
- 上面演示了在同一函数中指定泛型类型参数、trait bounds 和生命周期的语法;
- 示例返回两个字符串 slice 中较长者的函数且带有一个额外的参数 ann;
- ann 的类型是泛型 T,它可以被放入任何实现了 where 从句中指定的 Display trait 的类型;
- 这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来(因此Display trait bound是必须的);
- 生命周期也是泛型,所以生命周期参数 'a 和泛型类型参数 T 都位于函数名后的同一尖括号列表中;
这篇关于13.泛型、trait和生命周期(下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!