首页 > 其他分享 >《Beginning Rust From Novice to Professional》---读书随记(生命周期扩展)

《Beginning Rust From Novice to Professional》---读书随记(生命周期扩展)

时间:2022-11-28 20:33:08浏览次数:45  
标签:生命周期 u8 i32 Novice --- 引用 ri fn 随记

Beginning Rust From Novice to Professional

Author: Carlo Milanesi

如果需要电子书的小伙伴,可以留下邮箱,看到了会发送的

Chapter 23 More About Lifetimes

Lifetime Elision

trait Tr {
    fn f(x: &u8) -> &u8;
}

这个返回值就不需要标注生命周期,这时候默认在参数中选一个参数为参考,而这里只有一个参数

trait Tr {
    fn f(b: bool, x: (u32, &u8)) -> &u8;
}
trait Tr {
    fn f(x: &u8) -> (&u8, &f64, bool, &Vec<String>);
}

这些函数有一个特征,就是都可以找到唯一可用的生命周期

Lifetime Elision with Object-Oriented Programming

trait Tr {
    fn f(&self, y: &u8) -> (&u8, &f64, bool, &Vec<String>);
}

这时候,有两个引用入参,那么前面那个约定当然就不起效了,但是,如果是入参有self,那么默认就是使用的self的生命周期,也就是等价于下面:

trait Tr {
    fn f<'a>(&'a self, y: &u8) -> (&'a u8, &'a f64, bool, &'a Vec<String>);
}

也可以对选定的引用覆盖这类行为。比如说,下面的函数中,第二个返回值与y关联,第一个和第四个默认是和self关联

trait Tr {
    fn f<'a>(&self, y: &'a u8) -> (&u8, &'a f64, bool, &Vec<String>);
}

The Need of Lifetime Specifiers for Structs

struct S {
    _b: bool,
    _ri: &i32,
}
let _y: S;
let x: i32 = 12;
_y = S { _b: true, _ri: &x };

上面的代码,是错误的,其实和函数的检查方式差不多,因为之前一开始的时候,我们的代码是写在一个块里面的,函数内部是一个块,当然结构体的内部也是一个块,所以它们的检查也是差不多的。看代码,S结构体内部有一个引用字段,那么S的声明是在_y这一行,然后初始化是在x之后,并且引用了x,很明显,我们是按照变量的声明顺序释放对象,所以_y的生命周期是比x的长,所以编译报错

struct S {
    _b: bool,
    _ri: &i32,
}

fn create_s(ri: &i32) -> S {
    S { _b: true, _ri: ri }
}

// In application code:
fn main() {
    let _y: S;
    let x: i32 = 12;
    _y = create_s(&x);
}

接着看一个复杂一点的例子,代码中,函数虽然没有直接返回引用,但是返回了一个包含引用的结构体的实例,我们首先从内部开始向外看,S中的引用的生命周期与ri关联,那么就函数的签名和主体部分来看,生命周期不需要标注,这部分是对的,但是视角来到main函数中的时候,我们只需要看create_s的函数签名,返回值的结果与x的生命周期关联,也就是说,_y的生命周期应该比x要小,但是很明显不对,_y先声明的,所以,编译报错

struct S {
    _b: bool,
    _ri: &'static i32,
}

fn create_s(ri: &i32) -> S {
    static ZERO: i32 = 0;
    static ONE: i32 = 1;
    S {
        _b: true,
        _ri: if *ri > 0 { &ONE } else { &ZERO },
    }
}

// In application code:
fn main() {
    let _y: S;
    let x: i32 = 12;
    _y = create_s(&x);
}

然后看一个改良版的代码,这次,S的引用的生命周期标注是静态对象,那么其实这时候,函数create_s的返回值的生命周期和入参就没有关联了,把这个S当作是没有引用的结构体就可以了,因为内部的引用的生命周期与程序挂钩

Possible Lifetime Specifiers for Structs

实际上,对于结构的引用字段的生命周期,Rust编译器只允许两种可能性:

  • 这样的字段只允许引用静态对象
  • 这样的字段允许引用静态对象或尽管不是静态对象,但在整个结构对象之前存在并且比它寿命更长的对象
// In some library code:
struct S<'a> { _b: bool, _ri: &'a i32 }
fn create_s<'b>(ri: &'b i32) -> S<'b> {
    S { _b: true, _ri: ri }
}

// In application code:
fn main() {
    let x: i32 = 12;
    let _y: S;
    _y = create_s(&x);
}

_ri存储在结构体中,不需要检查“create_s”函数的主体,也不需要检查“S”结构体的字段列表;只要检查create_s函数的签名和S的签名,即在开括号之前的声明部分就足够了

Other Uses of Lifetime Specifiers

struct S<'a, T> { 
    b: &'a T 
}

这是错误的,编译报错:"the parameter type T may not live long enough"

这样做的原因是泛型类型T可以被包含引用的类型具体化,而这样的引用可能会导致生命周期错误。为了防止它们,编译器禁止这种语法。

  • 由“T”表示的类型将不包含引用,或者它将只包含对静态对象的引用;
  • 由“T”表示的类型可以包含对非静态对象的引用,其中必须指定其生命周期;

第一种:

struct S<'a, T: 'static> { b: &'a T }
let s = S { b: &true };
print!("{}", *s.b);

第二种:

struct S<'a, T: 'a> { b: &'a T }
let s1 = S { b: &true };
let s2 = S { b: &s1 };
print!("{} {}", *s1.b, *s2.b.b);

在第一行中,“T”类型参数被限制为“a”生命周期说明符,这意味着这种类型,无论它是什么,都可能包含一个引用,该引用借用了已经由该生命周期说明符注释的相同对象,即整个结构对象本身。

在第二行中,“S”结构体被实例化了,它隐式地指定“bool”为它的“T”类型参数。实际上,这种类型不包含任何引用,因此对于这一行,一个静态边界就足够了,如前面的示例程序所示。

但是在第三行中,“S”结构被实例化,隐式地指定S<bool>作为它的“T”类型参数。实际上,这种类型确实包含一个非静态引用,因此对于这一行,静态边界是不够的。

标签:生命周期,u8,i32,Novice,---,引用,ri,fn,随记
From: https://www.cnblogs.com/huangwenhao1024/p/16933521.html

相关文章