在Rust
中,函数签名类似“讲故事”。经验丰富的Rust
程序员,只需浏览一个函数的签名,就可以知道该函数大部分的行为。
在本文中,我们将探讨一些函数签名,并讨论如何读它们并从中提取信息。在探索的同时,你可以在 Rust API 文档中找到许多出色的函数签名示例。你也可以在 练习场 实践。
“婴儿起步”
你在Rust
中的定义的第一个函数,几乎是这样的:
fn main() {}
那我们就从这里开始吧!
-
fn
:是告诉Rust
,我们声明一个函数的语法。 -
main
:是函数的名词。只是main
是特殊的,它是在构建和运行二进制程序时调用的。函数名称总是蛇形命名snake case
,而不是驼峰命名camel case
。 -
()
:是参数列表。示例表示,main
不接受任何参数。 -
{}
:是函数的分隔符。示例表示,函数体是空的。
可见性
默认情况下,所有函数都是私有的,不能在其所在的模块之外使用它们。但使它们可以由不同模块使用,是件简单的事。
mod dog {
fn private_function() {}
pub fn public_function() {}
}
// 可选的,避免使用foo::
use dog::public_function;
fn main() {
dog::public_function();
// 如果使用use方式,就可以这样调用
public_function();
}
就像可变性一样,Rust
在可见性上的假定也是保守的。如果你尝试使用私有函数,编译器将让你知道并帮助你,它会指出哪个函数需要设置为public
。
如果你的项目中有一个像foo::bar::baz::rad()
这样的函数,并希望可以用foo::rad()
来使用它,请添加pub use bar::baz::rad;
到你的foo
模块。这称为重新导出。
简单的参数
不再满意do_nothing_useful()
函数,你决定收养一只狗。祝你好运!现在你遇到了一个新问题,你必须遛它并陪它玩!
fn walk_dog(dog_name: String) {}
fn play_with(dog_name: String, game_name: String) {}
参数声明,变量名: 类型
,多个参数以逗号分隔。但继续,我们的狗不是一个字符串!好消息,你也可以使用自己的类型。
struct Dog; // 简单起见,我们用空结构体
struct Game;
fn walk_dog(dog: Dog) {}
fn play_with(dog: Dog, game: Game) {}
很好,看起来已经比较好了。让我们开始那美好的一天吧。
fn main() {
let rover = Dog;
walk_dog(rover);
let fetch = Game;
play_with(rover, fetch); // 编译错误!
}
哇哇!这是一个完美的好日子,编译器完全毁了我们!Rover
将会非常难过。
我们来看看错误:
error[E0382]: use of moved value: `rover`
--> src/main.rs:12:15
|
8 | let rover = Dog;
| ----- move occurs because `rover` has type `Dog`, which does not implement the `Copy` trait
9 | walk_dog(rover);
| ----- value moved here
...
12 | play_with(rover, fetch);
| ^^^^^ value used here after move
编译器告诉我们,当我们将rover
传递给walk_dog()
时,它的所有权被转移了。这是因为fn walk_dog(dog: Dog){}
接受Dog
值时,我们没有告诉编译器它们是可复制的!传递参数给函数时,可复制的值会被隐式复制。你可以通过在类型的声明上方添加#[derive(Copy)]
来实现可复制。
我们要保持狗不可复制,因为,天哪,你不能复制狗。那么我们如何解决这个问题呢?
我们可以克隆rover
。但我们的Dog
结构体也不是Clone
的!克隆意味着我们可以明确地制作一个对象的副本。你可以像复制一样实现克隆。要克隆我们的狗,你可以rover.clone()
。
但实际上,这些可能的解决方案都没有解决真正的问题:我们想和同一只狗一起走路和玩耍!
借用
我可以借你的狗吗?
代替将我们的Dog
移动到walk_dog()
函数中,我们只想借用我们的Dog
到函数中。当你遛狗时,通常狗最终会和你一起回到家里,对吧?
Rust
使用&
来表示借用。借用某个值告诉编译器,当函数调用完后,值的所有权将返回给调用者。
fn walk_dog(dog: &Dog) {}
fn play_with(dog: &Dog, game: Game) {}
有不可变借用以及可变借词(&mut
)。你可以将一个不可变借用传递给任意数量的对象,而可变借用一次只能传递给一个对象。这确保了数据的安全性。
所以我们新的借用功能并没有真正解决问题,不是吗?我们甚至不能改变狗!让我们试着看看错误信息。
error[E0594]: cannot assign to `dog.walked` which is behind a `&` reference
--> src/main.rs:6:5
|
5 | fn walk_dog(dog: &Dog) {
| ---- help: consider changing this to be a mutable reference: `&mut Dog`
6 | dog.walked = true;
| ^^^^^^^^^^^^^^^^^ `dog` is a `&` reference, so the data it refers to cannot be written
将函数签名更改为fn walk_dog(dog: &mut Dog){}
,并更新我们的main()
函数,我们可以解决这个问题。
fn main() {
let mut rover = Dog { walked: false };
walk_dog(&mut rover);
assert_eq!(rover.walked, true);
}
正如你所看到的,函数签名告诉程序员一个值是否可变以及该值是否已被使用或引用。
返回值
让我们重新审视我们如何获得Rover
,这是我们探索如何返回类型!假设我们想要一个函数adopt_dog()
,它接收一个名字,并返回给我们一只Dog
。
struct Dog {
name: String,
walked: bool,
}
fn adopt_dog(name: String) -> Dog {
Dog { name: name, walked: false }
}
fn main() {
let rover = adopt_dog(String::from("Rover"));
assert_eq!(rover.name, "Rover");
}
所以函数签名中的-> Dog
部分告诉我们函数返回一个Dog
。请注意,名称name
将转移并赋值给Dog
,而不是复制或克隆。
内置trait
如果你在trait
中实现函数,你可以访问以下两个“元素”:
-
Self
,类型,表示当前类型。 -
self
,参数,指定结构体实例的借用/移动/可变性。
在下面的walk()
中,我们采取可变借用,self
移动值。一个例子:
// 沿用上面的Dog结构体
impl Dog {
pub fn adopt(name: String) -> Self {
Dog { name: name, walked: false }
}
pub fn walk(&mut self) {
self.walked = true
}
}
fn main() {
let mut rover = Dog::adopt(String::from("Rover"));
assert_eq!(rover.name, "Rover");
rover.walk();
assert_eq!(rover.walked, true);
}
泛型
在我们现实生活中,会有很多不同种类的狗!还有很多类型的动物!其中一些我们可能也想遛,比如我们的熊。
泛型可以让我们这样做。我们可以有实现Walk
特性的Dog
和Bear
结构体,然后让walk_pet()
函数接受任何具有Walk
特性的结构体!
在函数名称和参数列表之间,可以使用尖括号指定泛型的名称。关于泛型的重要注意事项是,当你接受泛型参数时,你只能使用函数中约束的类型。这意味着如果将Read
传递给想要Write
的函数,除非约束包含它,否则它仍然无法读入Read
。
struct Dog { walked: bool, }
struct Bear { walked: bool, }
trait Walk {
fn walk(&mut self);
}
impl Walk for Dog {
fn walk(&mut self) {
self.walked = true
}
}
impl Walk for Bear {
fn walk(&mut self) {
self.walked = true
}
}
fn walk_pet<W: Walk>(pet: &mut W) {
pet.walk();
}
fn walk_pet_2(pet: &mut Walk) {
pet.walk();
}
fn main() {
let mut rover = Dog { walked: false, };
walk_pet(&mut rover);
assert_eq!(rover.walked, true);
}
你还可以使用不同的方式,where
语法来指定泛型,因为复杂泛型的函数签名可能会变得相当长。
fn walk_pet<W>(pet: &mut W)
where W: Walk {
pet.walk();
}
如果有多个泛型,你可以用逗号分隔它们。如果你有多个特性约束,你可以使用加号组合它们。
fn stuff<R, W>(r: &R, w: &mut W)
where W: Write, R: Read + Clone {}
看看你可以从该函数签名中获得的所有信息!虽然函数名没有帮助性信息,但你还是几乎可以确定它的作用!
还有一些称为关联类型的内容,它们用于像Iterator
这样的事物。当书写函数签名时,你想使用像Iterator<Item = Dog>
这样的语句来表明一个Dog
的迭代器。
传递函数
有时需要将函数传递给其他函数。在Rust
中,接受函数作为参数是相当简单的。函数具有特征,它们像泛型一样传递!
在这种情况下,你应该使用where
语法。
struct Dog {
walked: bool
}
fn do_with<F>(dog: &mut Dog, action: F)
where F: Fn(&mut Dog) {
action(dog);
}
fn walk(dog: &mut Dog) {
dog.walked = true;
}
fn main() {
let mut rover = Dog { walked: false, };
// 函数
do_with(&mut rover, walk);
// 闭包Closure
do_with(&mut rover, |dog| dog.walked = true);
}
Rust
中的函数实现特性,编译器会检测它们是如何传递的:
-
FnOnce
- 采用按值(T)方式接受。 -
FnMut
- 采用可变引用(&mut T)方式接受。 -
Fn
- 采用不可变引用(&T)方式接受。
闭包|...| ...
将自动实现(在满足使用需求的前提下)尽量以限制最多的方式捕获。
- 所有闭包实现
FnOnce
:如果闭包仅实现FnOnce
,则只能调用一次。 - 不转移捕获变量所有权的闭包实现
FnMut
,允许多次调用它们。 - 不需要对其捕获变量唯一/可变访问的闭包实现
Fn
,允许它们在任何地方被调用。
生命周期Lifetimes
你现在可能自我感觉良好。我的意思是,看看那个滚动条,它几乎到了页面的底部!你很快就会成为Rust
函数签名大师!
让我们谈谈一些有关生命周期的话题,因为你最终会遇到它们,并且可能会变得很困惑。
让我在这里诚实地对你说。生命周期对我来说是一种神秘的艺术。我在Rust 0.7-0.10
使用了它们,之后我就没使用它们了。如果你真的知道任何关于它们的事情,你就比我更有资格写这个部分了。
现代Rust
有一个非常强大和有效的生命周期,它去掉了我们过去需要关注的绝大多数生命周期的“体力活”。
所以,如果你开始处理很多生命周期,你的第一步应该是坐下来思考它。除非你的代码非常复杂,否则你很可能不需要处理生命周期。如果你在一个简单的例子中碰到生命周期,你的问题可能是不正确的。
这是一个Option
实现的具有生命周期的函数。
as_slice<'a> (&'a self) -> &'a [T]
生命周期用'
表示,并给出一个名称。这里是'a
。但如果你更喜欢开笑话,它们也可以是'burrito
。基本上这个函数签名是说:调用Option<T>
的生命周期与返回的[T]
的生命周期相同。
挑战时间
下面,你将看到从标准库中提取的一组函数以及指向其文档的链接。你能从他们的函数签名中看出他们做了什么吗?为了增加乐趣,我删除了函数名!
fn name<P: AsRef<Path>>(path: P) -> Result<File>
源
fn name<E, T>(self, err: E) -> Result<T, E>
源
// In `Iterator<Item=T>`
fn name<B: FromIterator<Self::Item>>(self) -> B
where Self: Sized
源
fn name<B, F>(self, init: B, f: F) -> B
where Self: Sized, F: FnMut(B, Self::Item) -> B
源
fn name<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F>
源
我希望这是奇妙的,我在这里为你欢呼!
本文翻译自“Andrew Hobden - Reading Rust Function Signatures”