Trait
Trait又被成为共同行为,和其他oop语言的接口定义类似,用抽象的方式定义一个可以共享的行为,其他类型可以用自己的方式实现这个 Trait。
通过 trait
关键字可以定义一个 trait ,trait内部可以包含多个函数的定义,但是不要求实现,当然你也可以为 trait 中的方法提供一个默认的实现
pub trait Testa {
fn fa(&self) -> i32;
}
//有默认实现
pub trait Testa {
fn fa(&self) -> i32 {
1
}
}
我们在 定义的类型上使用 for
这个关键字就可以实现对应的 trait ,如果这个 trait 具有默认实现,那么新实现的方法将会覆盖默认实现,这类操作被称为重载
pub struct Test {
pub x: i32,
}
impl Testa for Test {
fn fa(&self) -> i32 {
self.x
}
}
要注意的是,需要实现的 trait 和需要实现这个 trait 的数据类型,至少有一个要在本地,这样做可以防止其他人破坏你的代码,被称为 “孤儿原则”。
Trait 参数
在设定参数时,我们可以指定传入一个实现了某些 trait 的类型的参数,比如下面的例子,可以传入一个实现了 Testa 这个trait 的参数
pub fn tb(item: &impl Testa) {
println!("{}", item.fa());
}
同样,我们的泛型也可以规定其实现指定的 trait ,比如下面的例子,需要 T 的泛型是实现了 Testa 这个 trait 的
pub fn tb<T: Testa>(item: &T) {
println!("{}", item.fa());
}
如果需要一个参数实现多个 trait 可以使用 + 符号来定义
pub fn tb(item: &impl Testa + Testb)
这种定义可以使用 where
关键字来简化
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
{
生命周期
Rust 中的每一个引用都有其生命周期(lifetime),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,但是引用可能有多种结果的时候,引用的生命周期可能是不一定的,这个时候不能直接推断出生命周期,这个时候就需要我们进行生命周期的标注:
如下的函数,传入两个字符串切片类型,返回一个字符串切片,根据逻辑,返回值要么指向 x,要么指向 y,但是 Rust 并不知道将要返回的引用是指向 x
或 y
,所以这里无法推断出生命周期:
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);
}
}
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
为了修复这个错误,需要增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析。需要使用生命周期标注 'a
,可以将参数和返回值的关联起来,如下的例子,生命周期标注定义完毕后,他们都是与生命周期 'a
存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 'a
存在的一样长的字符串 slice。它的实际含义是 longest
函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致。
//定义一个生命周期 &'a i32
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
当具体的引用被传递给 longest
时,被 'a
所替代的具体生命周期是 x
的作用域与 y
的作用域相重叠的那一部分。换一种说法就是泛型生命周期 'a
的具体生命周期等同于 x
和 y
的生命周期中较小的那一个。因为我们用相同的生命周期参数 'a
标注了返回的引用值,所以返回的引用值就能保证在 x
和 y
中较短的那个生命周期结束之前保持有效。
//这种情况是不能通过的,因为string2的生命周期小,所以我们的返回值的生命周期就是string2的生命周期范围
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);
}
指定生命周期参数的正确方式依赖函数实现的具体功能,只有与返回值相关的参数需要生命周期。如将 longest
函数的实现修改为总是返回第一个参数而不是最长的字符串 slice,就不需要为参数 y
指定一个生命周期。
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
而且 Rust 也不允许我们创建一个悬垂引用,如果它指向一个函数内部创建的值,它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。尝试考虑这个并不能编译的 longest
函数实现:
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
}
如果我们将定义包含引用的结构体,这需要为结构体定义中的每一个引用添加生命周期注解。如果下面例子,这个注解意味着 ImportantExcerpt
的实例不能比其 part
字段中的引用存在的更久。它存放了变量 novel
所拥有的 String
的第一个句子的引用。novel
的数据在 ImportantExcerpt
实例创建之前就存在。另外,直到 ImportantExcerpt
离开作用域之后 novel
都不会离开作用域,所以 ImportantExcerpt
实例中的引用是有效的。
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,
};
}
当为带有生命周期的结构体实现方法时。结构体字段的生命周期必须总是在 impl
关键字之后声明并在结构体名称之后被使用,因为这些生命周期是结构体类型的一部分。
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
生命周期省略
Rust 团队发现在特定情况下 Rust 程序员们总是重复地编写一模一样的生命周期注解,这些场景是可预测的并且遵循几个明确的模式,被编码进 Rust 引用分析的模式被称为 生命周期省略规则(lifetime elision rules)。规则如下:
- 有一个引用参数的函数有一个生命周期参数:
fn foo<'a>(x: &'a i32)
,有两个引用参数的函数有两个不同的生命周期参数,fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
,依此类推。 - 只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:
fn foo<'a>(x: &'a i32) -> &'a i32
。 - 第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是
&self
或&mut self
,说明是个对象的方法(method),那么所有输出生命周期参数被赋予self
的生命周期。
如下面的例子:开始时签名中的引用并没有关联任何生命周期:
fn first_word(s: &str) -> &str {
接着编译器应用第一条规则,也就是每个引用参数都有其自己的生命周期。
fn first_word<'a>(s: &'a str) -> &str {
对于第二条规则,因为这里正好只有一个输入生命周期参数所以是适用的。第二条规则表明输入参数的生命周期将被赋予输出生命周期参数,所以现在签名看起来像这样:
fn first_word<'a>(s: &'a str) -> &'a str {
现在这个函数签名中的所有引用都有了生命周期,如此编译器可以继续它的分析而无须程序员标记这个函数签名中的生命周期。
静态生命周期
这里有一种特殊的生命周期:'static
,其生命周期能够存活于整个程序期间。所有的字符串字面值都拥有 'static
生命周期,我们也可以选择像下面这样标注出来,这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的。因此所有的字符串字面值都是 'static
的:
let s: &'static str = "I have a static lifetime.";