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特性的DogBear结构体,然后让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”