前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust学习:如何解读函数签名?

Rust学习:如何解读函数签名?

作者头像
MikeLoveRust
发布2019-09-03 11:23:07
2K0
发布2019-09-03 11:23:07
举报

Rust中,函数签名类似“讲故事”。经验丰富的Rust程序员,只需浏览一个函数的签名,就可以知道该函数大部分的行为。

在本文中,我们将探讨一些函数签名,并讨论如何读它们并从中提取信息。在探索的同时,你可以在 Rust API 文档中找到许多出色的函数签名示例。你也可以在 练习场 实践。

“婴儿起步”

你在Rust中的定义的第一个函数,几乎是这样的:

代码语言:javascript
复制
fn main() {}

那我们就从这里开始吧!

  • fn:是告诉Rust,我们声明一个函数的语法。
  • main:是函数的名词。只是main是特殊的,它是在构建和运行二进制程序时调用的。函数名称总是蛇形命名snake case,而不是驼峰命名camel case
  • ():是参数列表。示例表示,main不接受任何参数。
  • {}:是函数的分隔符。示例表示,函数体是空的。

可见性

默认情况下,所有函数都是私有的,不能在其所在的模块之外使用它们。但使它们可以由不同模块使用,是件简单的事。

代码语言:javascript
复制
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()函数,你决定收养一只狗。祝你好运!现在你遇到了一个新问题,你必须遛它并陪它玩!

代码语言:javascript
复制
fn walk_dog(dog_name: String) {}
fn play_with(dog_name: String, game_name: String) {}

参数声明,变量名: 类型,多个参数以逗号分隔。但继续,我们的狗不是一个字符串!好消息,你也可以使用自己的类型。

代码语言:javascript
复制
struct Dog;  // 简单起见,我们用空结构体
struct Game;

fn walk_dog(dog: Dog) {}
fn play_with(dog: Dog, game: Game) {}

很好,看起来已经比较好了。让我们开始那美好的一天吧。

代码语言:javascript
复制
fn main() {
    let rover = Dog;
    walk_dog(rover);

    let fetch = Game;
    play_with(rover, fetch); // 编译错误!
}

哇哇!这是一个完美的好日子,编译器完全毁了我们!Rover将会非常难过。

我们来看看错误:

代码语言:javascript
复制
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使用来表示借用。借用某个值告诉编译器,当函数调用完后,值的所有权将返回给调用者。

代码语言:javascript
复制
fn walk_dog(dog: &Dog) {}
fn play_with(dog: &Dog, game: Game) {}

有不可变借用以及可变借词(&mut)。你可以将一个不可变借用传递给任意数量的对象,而可变借用一次只能传递给一个对象。这确保了数据的安全性。

所以我们新的借用功能并没有真正解决问题,不是吗?我们甚至不能改变狗!让我们试着看看错误信息。

代码语言:javascript
复制
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()函数,我们可以解决这个问题。

代码语言:javascript
复制
fn main() {
    let mut rover = Dog { walked: false };
    walk_dog(&mut rover);
    assert_eq!(rover.walked, true);
}

正如你所看到的,函数签名告诉程序员一个值是否可变以及该值是否已被使用或引用。

返回值

让我们重新审视我们如何获得Rover,这是我们探索如何返回类型!假设我们想要一个函数adopt_dog(),它接收一个名字,并返回给我们一只Dog

代码语言:javascript
复制
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移动值。一个例子:

代码语言:javascript
复制
// 沿用上面的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

代码语言:javascript
复制
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语法来指定泛型,因为复杂泛型的函数签名可能会变得相当长。

代码语言:javascript
复制
fn walk_pet<W>(pet: &mut W)
where W: Walk {
    pet.walk();
}

如果有多个泛型,你可以用逗号分隔它们。如果你有多个特性约束,你可以使用加号组合它们。

代码语言:javascript
复制
fn stuff<R, W>(r: &R, w: &mut W)
where W: Write, R: Read + Clone {}

看看你可以从该函数签名中获得的所有信息!虽然函数名没有帮助性信息,但你还是几乎可以确定它的作用!

还有一些称为关联类型的内容,它们用于像Iterator这样的事物。当书写函数签名时,你想使用像Iterator<Item = Dog>这样的语句来表明一个Dog的迭代器。

传递函数

有时需要将函数传递给其他函数。在Rust中,接受函数作为参数是相当简单的。函数具有特征,它们像泛型一样传递!

在这种情况下,你应该使用where语法。

代码语言:javascript
复制
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]的生命周期相同。

挑战时间

下面,你将看到从标准库中提取的一组函数以及指向其文档的链接。你能从他们的函数签名中看出他们做了什么吗?为了增加乐趣,我删除了函数名!

代码语言:javascript
复制
fn name<P: AsRef<Path>>(path: P) -> Result<File>

代码语言:javascript
复制
fn name<E, T>(self, err: E) -> Result<T, E>

代码语言:javascript
复制
// In `Iterator<Item=T>`
fn name<B: FromIterator<Self::Item>>(self) -> B
where Self: Sized

代码语言:javascript
复制
fn name<B, F>(self, init: B, f: F) -> B
where Self: Sized, F: FnMut(B, Self::Item) -> B

代码语言:javascript
复制
fn name<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F>

我希望这是奇妙的,我在这里为你欢呼!

本文翻译自“Andrew Hobden - Reading Rust Function Signatures”,点击「阅读原文」查看。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • “婴儿起步”
  • 可见性
  • 简单的参数
  • 借用
  • 返回值
  • 内置trait
  • 泛型
  • 传递函数
  • 生命周期Lifetimes
  • 挑战时间
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档