这里有一段 Rust 的函数签名:
这是一段在 async_trait
下生成的代码,不得不吐槽 Rust 的这些特性太令人难懂了,新手学 Rust 肯定会被劝退。但这里不妨详细解释这段函数签名。
function
这是 Rust 里最简单的函数语法:
fn foo(id: Uuid) -> usize {
...
}
很明显上图里的东西要比这个复杂的多,我们一个一个来看。
self
在函数签名里有一个 &self
参数,它表示所在结构体的语法糖。如果你有其他面向对象的语言经验的话,应该很熟悉 class 和 class 下面定义方法的语法,然后在 new
出一个示例后可以通过 .
(点)操作符来使用这个类下面的方法。
在 Rust 里类似。
struct Foo {}
impl Foo {
pub fn get(&self) -> usize {
...
}
}
let foo = Foo{};
foo.get();
这个语法其实是通过 self
隐藏了一个自身的对象,我们把他展开就是这样的:
let foo = Foo{};
Foo::get(&foo);
从一个对象调用的形式变成了一个不同的函数调用的形式,函数签名中的 self
,就表示一个函数所在的 struct 的对象,self
前面的 &
对这个对象的不可变借用。同理,self
(获取了所有权的对象)还可以是 self
,&mut self
(可变借用)。
...
fn get(&mut self) {}
let mut foo = Foo{};
Foo::get(&mut foo);
...
fn get(self {}
let foo = Foo {};
Foo::get(foo);
// get 获取了所有权,foo 失效
lifetime
在函数名后紧跟着的有两个泛型参数:
fn get_administractor_by_id<'life0, 'async_trait>(........
单引号开头的泛型是生命周期注解(生命周期也是泛型,但叫生命周期注解),生命周期用在借用上,用于告诉编译器这个借用会持续多长时间,编译器会保证借用在这个生命周期内有效,而不变成悬垂指针。
生命周期是复杂的概念,来慢慢解释。
借用
如果一个函数的入参有一个借用的参数:fn foo(input: &Foo) {}
,可以把 input 当作是一个指向 Foo 的指针,如果在 foo 执行中 Foo 被释放了,函数 foo 中的 input 参数就会变成一个悬垂指针,这是 Rust 所不允许的。幸好 Rust 提供了生命周期注解,我们给加上注解:
fn foo<'a>(input: &'a Foo) {}
单引号开头的泛型是生命周期,函数 foo 定义了一个生命周期 'a
(你可以起任何名字),它表示函数 foo 的生命周期,而参数中 input: &'a Foo
,借用参数被标记了 'a
生命周期,表示这个借用 input,必须在 'a
内有效,而 'a
表示函数 foo 的生命周期,所以也等于 input 必须在函数 foo 内有效。
让我们画张图。
let input = Foo; // ------------------------
// --- |
foo(&input); // | foo 生命周期 | input 生命周期
//---- |
上面是有效的,因为 input
存活的时间比 foo
长。
注解
好了我们来看图片里的生命周期 'life0, 'async_trait
,表示函数定义了两个生命周期 'life0
,async_trait
。
先来看 'life0
,标注在 &'life0 self
参数上,表示 &self
的存活时间必须比函数长。同时看 where
部分,where
部分用于泛型约束,它约束了生命周期 'life0
和 'async_trait
的关系:'life0: 'async_trait
,意思是生命周期 'life0
必须大于 'async_trait
。然后看下一行约束:Self: 'async_trait
,表示 Self 对象收到 'async_trait
生命周期约束,即是 Self 必须存活比 'async_trait
久。
最后一行的约束里看到 T 受到了 'static
的生命周期约束,'static
我们应该很熟悉,是表示全局生命周期,但是在这里不一定是表示该对象必须在整个应用程序间存活。
'static
'static
生命周期标注的对象表示在整个应用程序间存活。,如果你写过字面量字符串:let t: &'static str = "abc";
,这个字符串受到 'static
约束,在整个应用程序间存活。但是当 'static
标注在泛型上时候却有另外的含义。
fn foo<T: 'static>(input: T) {}
上面的泛型 T 作为入参,同时受到 'static
约束,但是它不是表示 T 必须在整个应用程序间存活,我们可以在临时作用域中这样调用它:
fn main() {
{
let t = String::new();
foo(t);
// t drop here
}
}
fn foo<T: 'static>(input: T) {}
很明显变量 t 并不能在整个应用程序间存活,但是受到 'static
约束的 T 仍然接收了这个 t。你可以认为 'static
标注的泛型表示获取了 T 的所有权。
稍微修改一下代码,向 'static
传递借用:
fn main() {
// cli::run_cli(migration::Migrator).await;
{
let t = String::new();
foo(&t);
}
println!("drop")
}
fn foo<T: 'static>(input: T) {}
编译器弹出了一个错误:
error[E0597]: `t` does not live long enough
--> migration/src/main.rs:9:13
|
8 | let t = String::new();
| - binding `t` declared here
9 | foo(&t);
| ----^^-
| | |
| | borrowed value does not live long enough
| argument requires that `t` is borrowed for `'static`
10 | }
| - `t` dropped here while still borrowed
看起来好像是 t 的借用没有达到 'static
的要求,t 不是一个全局有效的静态变量,如果将 t 提升为一个静态变量 static t: String = String::new();
,那么确实可以解决编译错误。
但是编译器给出的错误信息并不是唯一的解法,'static
约束的 T 也表示需要 T 的所有权,所以也可以直接将临时作用域中的对象所有权传递进去。
就像我们常使用的 Box
,它有一个隐性的生命周期注解,Box<T: 'static>
。
回到图片中,函数的返回值里有一个 Box
,它是持有一个动态分配的 Future
对象,并且这个对象受到 'async_trait
约束,这表示这个对象必须是受到 'async_trait
约束的借用(必须存活比 'async_trait
久),或者持有所有权。
待续
这一篇先写到这里,下一篇继续,会介绍 Pin
,Future
。