昨天我们学了trait
今天我们继续往下学,该学生命周期了。
lifetime
)lifetime
也是之前mark
了很久的点,今天总算见到真面目了。
之前我们在学第四章《References and Borrowing》
[2] 的时候只是简单的提了一下。实际上任何的引用都有一个生命周期,它表示这个引用的有效范围,超出这个范围就挂了。
和type
的自动推断类似(大部分情况下),生命周期大部分情况下也是自动推断出来的。
当然,也有一些场景需要特别对待。
rust
要求我们用通用的lifetime
parameter
去注释(annotate
)关系(relationship
),这样能确保运行时的引用绝对有效。
这句话前半段有点怪。。。
这玩意儿在别的语言中很难说有或者类似的,有点陌生呢。。
dangling
)引用还记得第四章学的悬浮引用吗?
r
引用了x
,但是x
在第八行就挂了,这个时候r
引用的自然就不对了。
用今天学的知识来说就是x
的生命周期在第八行就结束了。
rust
中是如何推测它不允许这么做的呢?
borrow checker
)rust
在编译的时候会调用这个borrow checker
,它会对比作用域来推断(determine
)是否所有引用都是有效的(valid
)。
比如上面的那个例子
r
的生命周期是'a
,而x
是'b
,可以看到'a
的生命周期远大于'b
。
在r
借用x
的时候,borrow checker
就会比对它们的生命周期范围。
上面的代码改成下面这种方式就没问题了
可以看到这个时候x
的生命周期明显大于r
的。
这也就是说,只有被引用的对象生命周期大于等于引用它的变量时,就没问题。反之则编译报错。
我们先来看下例子
可以看到报错了,虽然x
和y
的类型都是一样的&str
,也就是返回的值类型已经确定是&str
一种,符合要求。
为什么报错了呢?
因为rust
并不知道你这个引用是否是一直有效的。
这个函数在哪里会被调用,传入的引用会在什么时候挂掉,这些都是不确定的。
那么该怎么解决这个问题呢?
lifetime annotation syntax
)在rust
中使用'a
,表示一个生命周期,比如以下用法
得放在&
引用符号后面。
当然,用'
后面的符号你想用啥字母都行。不过一般都是单个字符,因为它的名字一般不需要有意义,只是告诉rust
编译器这个参数关联的是哪个生命周期,一般都是'a
表示第一个引用的生命周期。
annotation
)上面那个问题我们直接来看下解决的办法
注释在函数名和()
之间,也就是和T
泛型的定义位置是一样的。
这样,x
和y
的生命周期就被约束为一样了,同时由于你return
的value
也约束了'a
的生命周期,所以它的有效范围和x
,y
的一致。
简单地说,就是x
,y
以及return
的value
都是类型一样,生命周期一样,那么在这个方法中compiler
就能确定它没问题了。
当然,如果你传入的参数的生命周期不一样,那还是会报错的,不过这个错误是写代码这人的错误了(来自borrow checker
的审判)。
在你注释生命周期之后,rust
只能帮你确认是否是相同作用域。
但如果你不声明,对不起,一定会报错。
可以看到和泛型一样(大部分),我们在调用的时候并不需要去手动传入,只要函数声明了参数的生命周期,编译器就会自动判断参数的上下文环境,然后推断出它们的命有多长。
来看下上面的例子不同作用域的情况
可以看到并没有报错。
为啥呢?
虽然x
和y
并不在同一个作用域,但是因为return
的value
是在最小的那个生命周期里,同时result
所在的作用域是最小的作用域,所以并没有啥问题。那么这个时候'a
生命周期范围就是最小的那个。
那么我们再来改动下代码
我们把result
放到main
这个作用域中,果然报错了。
因为x
和y
不在同一个作用域并且result
所在作用域和'a
不是同一个,那么这时候再将最小作用域当做生命周期范围就会有问题了,因为超出这个范围之后result
还被调用了。
这时只需要把println!
的语句迁移回最小作用域就没问题了。
这里你应该已经发现了,生命周期注释只是通知编译器这些个参数的命有多长,然后编译器调用borrow checker
帮你去判断这个参数能不能被雇佣。
要不要用到生命周期,完全取决于你这个function
是要做什么。
如果你这个函数里的参数并不和return
的value
或者其它任何参数有关联,那完全没必要去注释这个参数的生命周期。
不过上面这种情况可能会导致下面这种问题
也就是没有任何参数需要注释生命周期,这种时候如果你返回一个引用,那么这个引用一定会是一个悬浮引用,因为它的作用域就在函数里面,函数出调用栈的时候它就失效了。
即使我们注释了'a
这个生命周期也是没有用的,为什么呢?因为压根没有和'a
所在的作用域有生命周期关联。
这种问题完全就是开发者自己的问题了,rust
表示无能为力~
所以你只能改成返回这个值的所有权。
所以,要不要用到生命周期注释完全取决于你这个function
想干啥。
rust
搞了这套理论就是为了保证引用的正确性,防止引用指向错误的heap
内存空间。
我们之前提到生命周期的地方不止一处,在学strcut
的时候就提到过。
为什么struct
中也有这种问题呢?
因为struct
可以不拥有数据的所有权。
来看下下面这个例子
这段代码是完全没问题的,但是去掉'a
就会有问题了。和在function
中是一样的道理,rust
的编译器并不知道你这个struct
里的part
属性啥地方会被调用。
规范和T
泛型(generic
)一样。
现在我们知道了function
或者struct
中使用引用参数并且和return value
有染(误),那么这个时候大概率你就需要去声明生命周期。
但是,除了没有关联的场景外, 有些场景实际上没必要写引用。
比如:
first_word
这个method
就是使用了引用参数s
并且返回的数据和它有染。但是编译器并没有报错。为啥呢?
实际上这是一个历史问题,在rust
的早期版本pre-1.0
中,上面这段代码同样是会报错无法通过编译的。
但是,在一段时间的使用之后,rust
团队发现开发者们在某些特定的场景会一遍又一遍输入同一个生命周期,比如上面这个例子
开发者们每次都要去输入它们,就很烦,并且它们实际上是可以被推断出来的。
所以rust
开发团队把这部分可以直接推断出来的规则写进了compiler
中,这样开发者们就不用每次都去声明了。将来也有可能会加入更多的推断逻辑,这样就能减少更多的生命周期注释了。
rust
团队称其为*lifetime elision rules
。*
如果你的代码符合了里面的其中一种规则,那你完全没必要去注释生命周期了,毕竟这样有些麻烦。
当然,如果你的代码写的含糊不清,即使符合了规则,编译器无法识别也会报错。
而return
出去的叫做*output lifetimes
.*
如果你没有注释生命周期的话,rust
编译器会基于三条规则(三板斧)推断生命周期。也就是上面那个被插入到compiler
里的自动推测规则(lifetime elision rules
)。
第一条用于input lifetimes
,另外两条用于output lifetimes
。
如果三板斧都用完,还是没法推断出来,那对不起,直接报错处理。
output lifetimes
也就是return
,比如:&self
或者&mut self
,也就是说是一个method
,那么这个时候self
的生命周期将被分配给所有output lifetimes
。我们来模拟下编译器处理这块的逻辑,例子就用下面这个
毕竟它是一个命中规则的function
,所以用它再好不过了。
根据第一板斧,它将被转换成下面这个样子
先给所有input
分配一个lifetime
注释。
然后根据第二板斧,我们只有一个input
引用参数,那么这个参数的生命周期将被分配给output
这样就ok
了,第三板斧并不需要用到,毕竟function
没有self
。
我们再来换个例子
根据第一板斧,将变成
多input
,多lifetime annotation
。
然后根据第二板斧,不适用。
同样的第三板斧更不适用了,那么这个时候打不过只能跑了,直接报错处理。
我们再来看个例子,关于第三板斧的。
method
声明中使用生命周期注释和泛型的使用一样,直接来看下例子
我们定义了一个含有引用类型的struct
,然后又给这个struct
实现了两个method
。
其中level
方法返回的数据是一个i32
类型的,也就是说完全没有引用关联。可以看到我们的&self
,而不是&'a self
,因为没必要。但是impl
的'a
是不能省略的,会直接报错
是不允许的。
而announce_and_return_part
就符合我们的第三板斧,所以我们也没必要写,这里也不会报错。那么为什么这第三板斧不需要写注释呢?
其实这块逻辑挺好想通的,因为你的method
只能是这样调用struct_instance.xx()
。所以只要struct_instance
在哪,这个return value
也就在哪。
什么情况下可以用呢?比如字面量。
关键字眼'static
,为啥它是static
的呢?
因为这玩意儿直接hardcode
也就是硬编码到binary
里,所以这玩意儿会一直有效。
当然,大部分情况下你都不会用到。不过还是有的,比如
generic
)、trait bound
和lifetimes
第十章终于结束了。。。。
我们来将学的东西拼接到一起试下
没什么好说的,直接看例子即可。
Validating References with Lifetimesdoc.rust-lang.org/book/ch10-03-lifetime-syntax.html#summary
不知道说啥,自己看吧。。
好累、好冷
昨天吃了华莱士,肚子疼了一天。
然后又这么冷,搞得什么都不想干。
最后,如果这篇文章对你有帮助的话,点个赞可否?
发布于 2022-12-18 01:28・IP 属地广东