# rust-fsharp
**Repository Path**: ydyr/rust-fsharp
## Basic Information
- **Project Name**: rust-fsharp
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-05-11
- **Last Updated**: 2024-05-12
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 给FSharper的Rust, 给Rustacean的F#
# 这个仓库是干什么的?
这个仓库是给Rust和F#程序员用以互相了解的入门手册. 下面我来解释为什么为把这两个语言放在一起:
[Rust是我的第一语言](https://github.com/Dhghomon/programming_at_40/blob/master/README.md). 我最近在研究与Rust接近的语言, 其中之一是F#(因为Rust编译器原先是用OCaml写的, 并且它吸收了后者的很多语法).
几乎每份F#资料都假定读者是C#开发者. (在C#中你这么做, 但在F#中你这么做) 这个方法没什么问题, 但很多人包括我其实不了解C#. 而当我看到F#代码时我发现Rust要素到处都是: Option, Result, 匹配语句, 迭代器, 等等, 作为Rustacean它们的好处无需赘言. 让一个Rustacean读一本到处都提到C#的F#教材就好比让一个德国人用葡萄牙教材学荷兰语. 而对于FShaper来说, 默认读者不知道Option, 模式匹配等的Rust教程也令人不快.
既然两者如此相似, 给Rustacean的F#教程必然也可以作为F#程序员的Rust教程. 所以我写了这篇文档.
我随时可能在文中加入新想到的点子, 但考虑到我还不怎么熟悉F#, 欢迎在评论和PR中指出我的错误或措辞不当之处.
介绍到此为止. 开始正文吧!
# 免安装体验语言
两个语言都提供了免安装的在线运行环境, Rust有[Rust Playground](https://play.rust-lang.org/), 而F#的对应网站叫[Try F#](https://try.fsharp.org/).
# 基于表达式的语言
在两个语言中, 几乎所有东西都是表达式, 代码块中最后一个表达式会作为其返回值. Rust用花括号标明作用域, 而F#用空白符. 注意两者的相似性:
Rust:
```rust
fn main() {
let x = {
let x = 5;
let y = 10;
x + y // 最后一个表达式是返回值
// 不需要return关键字!
};
}
```
F#:
```fsharp
let x =
let x = 5
let y = 10
x + y
```
# 原始类型
Rust和F#都有类型推理. 它们都使用`let`绑定. 所以如果你这么写:
```rust
let x = 9;
```
在Rust中`x`会成为`i32`, 在F#中是`int`...其实也是一个32位有符号整型. 顺便, F#不要求在结尾放置分号, 但如果你放了, 编译器也不会抱怨. 没必要在上面的例子中添加逗号, 但如果你这么做了, 效果也一样:
```fsharp
let x =
let x = 5;
let y = 10;
x + y
```
(这是因为在两个情况下它都返回unit类型.)
*译注: 此处有误. F#中`let`不是单独的表达式, 而是更大表达式的一部分, 绑定与表达式主体以`in`或缩进连接. 比如*
```fsharp
let x = 1
x + 1
```
*与*
```fsharp
let x = 1 in x + 1
```
*是等价的. `;`在F#中用于连接两个表达式, 如*
```fsharp
printf "hello, "
printf "world"
```
*相当于*
```fsharp
printf "hello, "; printf "world"
```
*如果左边表达式的返回值不是`unit`, 编译器会发出警告*
让我们来看一些原始类型:
* Rust`u8`在F#中是`uint8`, 别名`byte`(.NET: `System.Byte`).
* Rust`u16`在F#中是`uint16`(.NET: `System.UInt16`)
* Rust`u32`在F#中是`uint32`(.NET: `System.UInt32`), 别名`uint`.
* Rust`u64`在F#中是`uint64`(.NET: `System.UInt64`)
* Rust`i8`在F#中是`int8` which is also an `sbyte`(.NET: `System.SByte`).
* Rust`i16`在F#中是`int16`(.NET: `System.Int16`)
* Rust`i32`在F#中是`int32`(.NET: `System.Int32`), 别名`int`.
* Rust`i64`在F#中是`int64`(.NET: `System.Int64`)
* Rust`usize`在F#中是`unativeint`(.NET: `System.UIntPtr`)
* Rust`isize`在F#中是`nativeint`(.NET: `System.IntPtr`)
* Rust`f32`在F#中是`float32`, 别名`single`(.NET: `System.Single`).
* Rust`f64`在F#中是`float`, 别名`double`(.NET: `System.Double`). 对于有小数部分的数字, Rust和F#都以此为默认浮点类型.
Rust和F#都用后缀标识32位整型和64位浮点以外的数字类型. 在Rust中你还可以用类型声明, 而在F#你只能用后缀.
* Rust`let x: u8 = 5`或`let x = 5u8`在F#中是`let x = 5uy`. (**u**nsigned b**y**te)
* Rust`let x: u16 = 5`或`let x = 5u16`在F#中是`let x = 5us`. (**u**nsigned **s**hort)
* Rust`let x: u32 = 5`或`let x = 5u32`在F#中是`let x = 5u`. (**u**nsigned)
* Rust`let x: u64 = 5`或`let x = 5u64`在F#中是`let x = 5UL`. (**U**nsigned **L**ong)
* Rust`let x: i8 = 5`或`let x = 5i8`在F#中是`let x = 5y`. (b**y**te)
* Rust`let x: i16 = 5`或`let x = 5i16`在F#中是`let x = 5s`. (**s**hort)
* Rust`let x: i32 = 5`或`let x = 5i32`在F#中是`let x = 5`. (默认情况, 没有后缀)
* Rust`let x: i64 = 5`或`let x = 5i64`在F#中是`let x = 5L`. (**L**ong)
* Rust`let x: f32 = 5.0`或`let x = 5.0f32`在F#中是`let x = 5.0f`或`let x = 5f`.
* Rust`let x: f64 = 5.0`或`let x = 5.0f64`在F#中是`let x = 5.0`. (默认情况, 没有后缀)
在Rust和F#中你都可以在数字中加入`_`以提升可读性. 下面的代码对两者都适用.
```
let num = 8000000;
let readableNum = 8_000_000;
let maybeReadableNum = 8___________000________000;
```
至于F#的`decimal`类型(.NET的`Decimal`), Rust是没有的. 准确地说, [它有](https://crates.io/crates/rust_decimal), 但不在标准库里. Rust标准库很小(甚至连随机数函数都没有), 十进制数由外部crate(大体上create = NuGet包)提供. 给Rust项目加入外部crate很简单, 只需要在`cargo.toml`里写一行:
```toml
[dependencies]
rust_decimal = "1.14" <-- 就这样
```
输入`cargo run`(类比`dotnet run`), 然后它就会下载外部包, 编译, 接着运行.
(显然`cargo.toml`的F#等价物是`paket.dependencies`.)
*译注: 更接近`xxx.fsproj`. paket是可选的.*
* Rust`char`在F#中是`char`(.NET`Char`). Rust`char`是UTF-32的, 永远有四个字节, 而F#的是UTF-16.
* Rust`unit`在F#中也是`unit`. 它们都用`()`作为其字面量. 这是令Rustacean十分开心的类型. (几乎)任何东西都是表达式!
* Rust`str`在F#中是`string`. F#文档叫它"字符串是用于表示文本的字符的顺序集合。 对象 String 是表示字符串的对象的顺序集合 System.Char".
* Rust`String`在F#中是[`StringBuilder`](https://docs.microsoft.com/zh-cn/dotnet/api/system.text.stringbuilder). F#文档: "表示可变字符字符串". 它用于高效构造不可变的`string`对象, 很像如下定义的`String`.
*译注: 上文所说"F#文档"实为.NET API参考和.NET基础知识文档*
```rust
pub struct String {
vec: Vec,
}
```
(给F#读者: `Vec`类似于[ResizeArray](https://twitter.com/ReedCopsey/status/1399492773900914691))
那么F#`string`的Rust等价物是:
```rust
pub struct String {
vec: Vec,
}
```
然而, .NET字符串是[不可变的](https://docs.microsoft.com/zh-cn/dotnet/api/system.string#Immutability):
```
对象 String 称为不可变(只读),因为创建对象后无法修改其值。
似乎修改 String 对象的方法实际上返回包含修改的新 String 对象。
由于字符串是不可变的,因此对单个字符串执行重复添加或删除操作的字符串
操作例程可能会造成重大性能损失。例如,以下代码使用随机数生成器创建
一个字符串,该字符串的范围为 1000 个字符,0x0001 0x052F。
尽管代码似乎使用字符串串联将新字符追加到命名 str的现有字符串,
但它实际上为每个串联操作创建一个新 String 对象。
```
这点上与`Vec`颇为不同, 后者在尾部追加`char`时仅需在超出容量时重新分配内存(这是自动完成的).
## 类型转换
在Rust中, 你可以用`as`把一个原始类型值转换到另一个类型.
```rust
fn print_isize(number: isize) { // 只接受isize
println!("{}", number);
}
fn main() {
let x: i8 = 9;
print_isize(x as isize); // 所以用as转换到isize
}
```
在F#中, 你可以在'变量'之前写上要转换的类型名. 譬如有一个函数只接受`byte`类型:
*译注: 这不是F#的特殊语法构造, 只是对`byte`函数(与`byte`类型重名)的简单函数调用. 准确地说, 这是一个内联函数, 没有额外的运行时开销.*
```fsharp
let printByte (number: byte) =
printfn "%i" number
```
(给Rust读者: `%i`代表打印一个整型. 如果你把它改成`%s`, 编译会失败)
然后给他一个写作`9`的数字, F#会认为它是一个`int`(Rust的`i32`), 你得把它转换成`byte`.
```fsharp
let printByte (number: byte) =
printfn "%i" number
let number = 9
printByte (byte number)
```
注意两者的区别: F#中我们得特地注明函数接受`byte`类型, 这样它才会编译失败; 否则它会用函数类型适配输入类型. Rust更加严格, 要求用泛型处理可变的类型. (F#也有泛型 - 二者区别见对应章节)
然而, F#在函数输入上更加聪明. 比如:
```fsharp
let printNumber number =
printfn "%i" number
let number = 9
printNumber number
```
编译器会根据`%i`和`number`上的let绑定推断出函数接受int, 然后将函数签名设为`number: int -> unit`. 但如果你加上一行:
```fsharp
let printNumber number =
printfn "%i" number
let number = 9
printNumber number // 给它一个int
printNumber (byte number) // 再给一个byte
```
这一回它就不会编译了, 因为它认为这个函数接受`int`, 我们却又让它接受`byte`类型. 前一个输入确定了输入类型, 所以它正确地拒绝了后者:
```
This expression was expected to have type
'byte'
but here has type
'int'
```
说到这点, 让我们来比较一下两个编译器对同一个问题发出的信息. 在F#写一个接受`int`的函数, 在Rust中写一个接受`i32`的, 分别用`byte`(Rust中的`u8`)调用它们, 看看编译器怎么说:
F#:
```fsharp
let printNumber (number: int) =
printfn "%i" number
let number = 9
printNumber (byte number)
```
Rust:
```rust
fn print_i32(number: i32) {
println!("{}", number);
}
fn main() {
let x: u8 = 9;
print_i32(x);
}
```
报错分别为:
F#
```
This expression was expected to have type
'byte'
but here has type
'int'
```
Rust
```
7 | print_i32(x);
| ^
| |
| expected `i32`, found `u8`
| help: you can convert a `u8` to an `i32`: `x.into()`
```
注意两则信息对同一事物的不同说法.
Rust: expected `i32`, found `u8` = **函数需要i32, 但你给了我u8**
F#: This expression was expected to have type 'byte' but here has type 'int' = **你的输入说明这是一个byte, 但函数是int类型的**
(Rust信息中提到的`.into()`是另一个类型转换的方法 - 后文详述)
在Rust中, `as`关键字常用于在存在重名或过长名字的作用域中重命名类型和枚举体等.
```rust
// 外部crate的奇怪枚举
enum ErrorTypes {
BigErrorExplanationThatIsTooLongToType,
WhyDidTheyWriteTheseEnumVariantsLikeThisSeriously,
ButThisIsPartOfExternalCodeINeedToCompile,
}
// 缩短它们
use ErrorTypes::{
BigErrorExplanationThatIsTooLongToType as ErrorOne,
ButThisIsPartOfExternalCodeINeedToCompile as ErrorTwo,
WhyDidTheyWriteTheseEnumVariantsLikeThisSeriously as ErrorThree,
};
fn main() {
let x = ErrorOne;
match x {
ErrorOne => {}
ErrorTwo => {}
ErrorThree => {}
}
}
```
## F#的`inline`关键字
(感谢[isaacabraham](https://github.com/Dhghomon/rust-fsharp/issues/1)的提供)
如果我们想让`printNumber`函数接受多种类型, 有一种让它变得泛型的方法: `inline`关键字. Rust也有`inine`, 但它是偶尔[用于优化](https://nnethercote.github.io/perf-book/inlining.html)的特性.
顺带一提, 两个语言的特性语法格式类似:
- Rust特性: `#[attribute_name]`
- F#特性: `[]`
回到`inline`: 在F#中它用于构造泛型代码. 看看[文档](https://docs.microsoft.com/zh-cn/dotnet/fsharp/language-reference/functions/inline-functions), 看着很眼熟:
```
内联函数是直接集成到调用代码中的函数。
使用内联函数
使用静态类型参数时,由类型参数参数化的任何函数都必须是内联函数。 这可保证编译
器可以解析这些类型参数。 使用普通泛型类型参数时,没有此类限制。
inline 的存在会影响类型推理。 这是因为内联函数可以具有静态解析的类型参数,而非
内联函数不能。
```
啊! 很像Rust的静态分派/单态化(单态化 = 让某物取单独一种表现形式): 也就是说, 代码中的某个位置提示编译器这个泛型函数取某个特定的类型参数时, 它会在编译时将其静态分派到这个具体类型. 让我们先来看看Rust泛型是怎么工作的.
```rust
use std::fmt::Display;
fn print_thing(input: T) {
println!("{}", input);
}
fn main() {
print_thing(8);
print_thing('a');
print_thing("Hiya there.");
}
```
[std::fmt::Display](https://doc.rust-lang.org/std/fmt/trait.Display.html)特型允许你用`{}`打印对应类型的某个值, 它的打印结果是人类可读的. 自己实现时, 你得用函数指导它该怎么打印. (另一方面, `Debug`添加个特性就可以实现, 它的输出更详尽但不好看)
*译注: F#也有两种显示方式: 一是与C#等兼容的`ToString`重写, 二是更详尽的`StructuredFormatDisplayAttribute`. F#自动为联合/记录等类型定义实现后者*
然后我们就有了一个`fn print_thing`, 它接受一个泛型参数`T`, 我们告诉编译器这个参数必须实现`Display`特型. Rust的所有原始类型都实现了`Display`. 既然我们告诉了编译器这一点, 它就会允许我们在`println!`里写`{}`, 因为它知道有`Display`的类型可以被显示. 接下来我们用三个类型分别调用: `i32`, `char`和`&str`.
程序成功编译, 发生了静态分派(单态化). 编译器分别生成了三个有具体类型的函数, 没有运行时开销. 编译结果类似这样:
```rust
fn print_thing_i32(input: i32) {
println!("{}", input);
}
fn print_thing_char(input: char) {
println!("{}", input);
}
fn print_thing_str(input: &str) {
println!("{}", input);
}
```
F#的`inline`关键字做的是差不多的事. 在使用`inline`之前, 我们的定义是这样的:
```fsharp
printNumber : number: int -> unit
let printNumber number =
printfn "%i" number
let number = 9
printNumber number
printNumber (byte number)
```
把鼠标光标悬停在`printNumber`上面, 编辑器告诉我们它是`printNumber: number: int -> unit`. 但如果加上`inline`:
```fsharp
let inline printNumber number =
printfn "%i" number
let number = 9
printNumber number
printNumber (byte number)
```
现在它变成了:
```
val printNumber : number:'a -> unit (requires 'a : (byte|int16|int32|int64|sbyte|uint16|uint32|uint64|nativeint|unativeint))
```
看起来这个泛型函数能处理任何整型输入, `inline`关键字让编译器给`printNumber number`单态化为一个接受`int16`的函数, 给`printNumber (byte number)`单态化一个接受`byte`的函数.
# 模式匹配
Rust和F#都使用关键字`match`. `match`的用法很相似, 但有一些不同. 下面是第一个:
如果你不匹配所有情况, Rust会编译失败. F#能编译, 但会给你一个警告:
Rust:
```rust
enum Mood {
Good,
Okay,
Bad
}
fn check_mood(mood: Mood) {
match mood {
Mood::Good => println!("Feeling good"),
Mood::Bad => println!("Not so good")
}
}
```
Rust产生如下报错:
```
error[E0004]: non-exhaustive patterns: `Okay` not covered
```
在F#类似的代码为:
```fsharp
type Mood = | Good | Okay | Bad
let myMood = Good
let matchMood = match myMood with
| Good -> printfn "Feeling good"
| Bad -> printfn "Not so good"
```
编译成功, 但我写这段文字的时候能看到: `Incomplete pattern matches on this expression. For example, the value 'Okay' may indicate a case not covered by the pattern(s).`
另外注意, F#代码的格式可以根据你的偏好而改变. 下面的代码是等价的:
```fsharp
type Mood =
| Good
| Okay
| Bad
let myMood: Mood = Good
let matchMood =
match myMood with
| Good -> printfn "Feeling good"
| Bad -> printfn "Not so good"
```
*译注: F#有一个社区格式化工具fantomas*
注意这里我们指定了类型`let myMood: Mood = Good`. F#编译器通过类型推理判断`Good`属于哪个类型; Rust不这么做. 所以如果你定义了一个字段相同的联合:
```fsharp
type Mood = | Good | Okay | Bad
type Mood2 = | Good | Okay | Bad
let myMood = Good
let matchMood =
match myMood with
| Good -> printfn "Feeling good"
| Bad -> printfn "Not so good"
```
它会使`myMood`变成`Mood2`(它重影了`Mood`). 当心! 不管字段顺序如何, 这都会发生:
```fsharp
type Mood = | Good | Okay | Bad
type Mood2 = | Bad | Good
let myMood = Good
let matchMood =
match myMood with
| Good -> printfn "Feeling good"
| Bad -> printfn "Not so good"
```
这里`myMood`也是`Mood2`的实例. Rustacean更倾向于写`let myMood: Mood`, 但记住, 如果你定义了字段相同的类型, 并且不在let绑定中声明类型, F#编译器不会自动为你补上.
*译注: 你可以给联合加上`[]`特性, 这样就必须使用类似Rust的限定访问: `Mood.Good`.*
有趣的是, F#还有一个`function`关键字, 它是`match (name) with`的缩写.
```fsharp
type Options =
| Sunny
| Rainy
| Other
let message = function
| Sunny -> printfn "It's sunny today"
| Rainy -> printfn "It's rainy"
| Other -> printfn "Not sure what the weather is."
message Sunny
```
这段代码打印"It's sunny today".
在[F#文档](https://docs.microsoft.com/zh-cn/dotnet/fsharp/language-reference/match-expressions)中, 你可以看到两者的例子:
```fsharp
// Pattern matching with multiple alternatives on the same line.
let filter123 x =
match x with
| 1 | 2 | 3 -> printfn "Found 1, 2, or 3!"
| a -> printfn "%d" a
// The same function written with the pattern matching
// function syntax.
let filterNumbers =
function | 1 | 2 | 3 -> printfn "Found 1, 2, or 3!"
| a -> printfn "%d" a
```
# 柯里化
Rust没有柯里化, 而在F#中它到处都是. 柯里化的意思是, 对于一个接受多个参数的函数, 可以一次只给它一个或部分参数. 对Rustacean来说, 它像是...
```rust
fn add_three(num_one: i32, num_two: i32, num_three: i32) {
num_one + num_two + num_three
}
```
但它的返回值不是`i32`, 而是函数运行所需的剩下参数. 给它一个`i32`(比如我们输入`add_three(8)`)然后它就知道了`num_one`的类型然后返回这个:
```rust
fn two_left(num_two: i32, num_three: i32) {
let num_one = 8;
num_one + num_two + num_three
}
```
(`two_left`这个名字只是个例子, 用来表示现在它长什么样)
现在如果你输入`two_left(9, 10)`, 它会完成函数调用, 输出`i32`, 返回最终结果27. 但如果只输入`two_left(9)`, 它会返回:
```rust
fn one_left(num_three: i32) -> i32 {
let num_one = 8;
let num_two = 9;
num_one + num_two + num_three
}
```
在F#中大概是这样:
```fsharp
let addThree a b c = a + b + c
let twoLeft = addThree 8
let oneLeft = twoLeft 9
printfn "%i" (oneLeft 10)
```
有趣的事实: 所有F#函数都是柯里化的, 所以我们会看到这样的奇怪签名:
```fsharp
val addThree : a: int -> b: int -> c: int -> int
```
当你向其中传入三个数字, 它会依次用一个参数柯里化这个函数, 直到最后三个参数都有了对应的值, 最后返回`int`(我的讲解可能有点不对劲 - 如果是的话告诉我)
在Rust中, 函数签名则是这样的:
```rust
fn add_three(a: i32, b: i32, c: i32) -> i32
```
注意F#不是为了耍酷才在参数间使用空格的: 它是柯里化语法. 如果你不想要函数被柯里化, 将参数放在元组里:
```fsharp
let addThree (a, b, c) = a + b + c
printfn "%i" (addThree (8, 9, 10))
```
FSharper不喜欢把东西写在括号里(尤其是嵌套括号), 喜欢管道运算符. 下面的代码更好看:
```fsharp
let addThree (a, b, c) = a + b + c
let finalNumber = (8, 9, 10) |> addThree
printfn "%i" finalNumber
```
那么让我们来看看管道运算符. Rust也有对应的东西.
# 管道运算符
Rust没有管道这个术语, 也没有`|>`运算符. 然而, 有些语法能模拟管道运算符的效果. 先来看看管道运算符是干什么的.
F#里(几乎)所有东西都是表达式, 这些东西都会有返回值, 即使是`()`类型. `|>`帮你方便地把返回值传递给别的函数. 下面是一个例子:
```fsharp
let addOne x = x + 1
let timesTwo x = x * 2
let printIt x = printfn "%A" x
8 |> addOne |> timesTwo |> printIt
```
上面有三个函数, 每一个都分别执行某个操作: 一个加上1, 下一个乘以2, 最后一个把它打印出来.
顺便: `%A`美化打印元组, 记录和联合. 这类似于Rust的`Debug`特型, 后者使用`{:?}`(`Debug`打印)而非`{}`(`Display`打印)格式化说明符. 使用`%A`可以避免选择`%i`(int), `%s`(string)等. 另一个说明符`%O`可以打印所有对象. (自动调用了`ToString()`)
Rust里对应(呃, 差不多对应)的东西是这样的:
```rust
fn add_one(x: i32) -> i32 {
x + 1
}
fn times_two(x: i32) -> i32 {
x * 2
}
fn print_it(x: T) {
println!("{:?}", x)
}
fn main() {
print_it(times_two(add_one(8)));
}
```
这里我们得输入一排括号进行调用. `T: std::fmt::Debug`是泛型的, 意思是告诉编译器"我保证你得到的是可以用`Debug`打印的".
如果你想用F#式从左到右的语法, 你可以创建一个结构体给它定义方法. 但对于这种简单情况这样做不怎么值得. 然而, 这种语法经常同迭代器组合出现, 后者被从左到右传递. 迭代器操作大概是Rust最'管道式'的语法了. 比如, 下面的代码取0到10的数字, 乘以二, 保留偶数, 然后保存到`Vec`里. 就像这样:
```rust
fn main() {
let times_two_then_even: Vec = (0..=10)
.map(|number| number * 2)
.filter(|number| number % 2 == 0)
.collect();
println!("{:?}", times_two_then_even);
}
```
打印出`[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]`.
*译注: 相比F#, 这其实更是C#风格的代码. 上面的例子在C#里这么写:*
```csharp
Enumerable.Range(0, 11)
.Select(x => x * 2)
.Where(x => x % 2 == 0)
.ToList();
```
*当然, F#也可以用C#的API:*
```fsharp
open System.Linq
(seq { 0..10 })
.Select(fun x -> x * 2)
.Where(fun x -> x % 2 = 0)
.ToList()
```
*另外, 还有一个社区的[FSharp.Core.Fluent](https://fsprojects.github.io/FSharp.Core.Fluent/)为F#内建类型扩展了这种语法.*
F#版本很像:
```fsharp
let timesTwoThenEven =
[0..10]
|> List.map (fun number -> number * 2)
|> List.filter (fun number -> number % 2 = 0)
printfn "%A" timesTwoThenEven
```
输出:`[0; 2; 4; 6; 8; 10; 12; 14; 16; 18; 20]`
注意区别: 两者都用了闭包/匿名函数, 但写法分别为:
Rust: `|variable_name_here| variable_name_here * 2`
F#: `(fun variable_name_here -> variable_name_here * 2)`
在Rust中, `||`用于定义闭包, `()`用于定义一般函数, 而在F#中, 一般函数没有特别的语法, 闭包使用`fun`关键字.
在两个例子中, 你都要给输入命名, 以进行下一步操作.
(顺便, Rust和F#都区分闭包(捕获了变量的函数)和匿名函数(不过是没有名字的函数), 但Rustacean倾向于把`||`包围的的东西都叫做闭包, 即使从技术角度来讲它是一个匿名函数. 甚至[书](https://doc.rust-lang.org/book/ch13-01-closures.html)上都说"**可以**捕获环境的匿名函数", 而非捕获**了**环境的匿名函数. 关于术语定义的讨论, 参见[这个issue](https://github.com/Dhghomon/easy_rust/issues/44))
Rust和F#还有一个重大区别: Rust有所谓"零开销抽象"的机制, 意味着优美的代码相比`for`循环等原始机制没有性能开销. 让我们再次考察Rust的`times_two_then_even`:
```rust
let times_two_then_even: Vec = (0..=10)
.map(|number| number * 2)
.filter(|number| number % 2 == 0)
.collect();
```
它返回一个`Vec`, 因为最后有`.collect()`方法. 如果我们不加上这个方法, 并且不保存到一个变量里的话:
```rust
fn main() {
(0..=10)
.map(|number| number * 2)
.filter(|number| number % 2 == 0);
}
```
编译成功, 但编译器说我们其实什么都没做:
```
warning: unused `Filter` that must be used
--> src/main.rs:2:5
|
2 | / (0..=10)
3 | | .map(|number| number * 2)
4 | | .filter(|number| number % 2 == 0);
| |__________________________________________^
|
= note: `#[warn(unused_must_use)]` on by default
= note: iterators are lazy and do nothing unless consumed
```
这是因为调用迭代器方法而不`collect`它们或保存到变量只能造成一个复杂的复合类型; 我们还没有`map`或`filter`任何东西. 让我们先试着激怒编译器:
```rust
fn main() {
let times_two_then_even: i32 = (0..=10) // 告诉编译器这是i32
.map(|number| number * 2)
.filter(|number| number % 2 == 0);
}
```
编译器抱怨道:
```
expected type `i32`
found struct `Filter