fn是哪里的网站,fn是啥

  

  我刚开始编程的时候,用的是PHP。个人觉得PHP的语法有点笨拙不自然,有时候甚至很烦人(为什么我要在每个变量前加$前缀?常量前面没有$。不还是可以理解的吗?学了其他语言之后,不喜欢PHP了,但是PHP的某些方面还是有吸引力的。比如数组循环很容易,有很多编程范式:函数式,面向对象,trait。   

  

  后来又学了JS,和C语言很像,随处可见。期间也做过一些Java和C#的项目,但后来又回到了JS。   

  

  我也尝试过学习C(和C)。虽然我拿到了Sololearn的证书,但是我从来没有真正使用过这两种语言。它们看起来很复杂:快速访问内存的功能很酷,但为什么我一定要用免费的?为什么它不知道超出范围就应该自动释放内存?   

  

  所以我更喜欢用JS编程,因为不需要考虑内存的问题。而且今天的IO相关操作也不会限制V8的速度。   

  

  后来听说了Rust,这是Mozilla开发的语言,多年来一直排在StackOverflow最喜欢的编程语言的首位,甚至超过了我最喜欢的Typescript(我喜欢Typescript主要是因为类型安全)。所以,我想我应该找个时间试试。   

  

     

  

     

  

  学习资源   

  

  我遇到的一个很大的问题,就是要找到好的资源,通俗易懂,短小精悍。我不喜欢youtube视频。我比较喜欢快速浏览一些文档,或者在上下班的路上看一些学习资源,也不占用太多流量。   

  

  以下是我找到的资源列表:   

  

  《The Rust Programming Language》(https://doc . rust-lang . org/book/):这是一本线上书籍,介绍了使用Rust可以实现的最常见的功能。   

  

  《A Gentle Introduction To Rust》(https://Steve Donovan . github . io/rust-gentle-intro/):简书,一两个小时就能看完,然后花一两天的时间去试例题。文章涉及的内容比较深入,但是很容易掌握。   

  

  https://www.reddit.com/r/rust/:这是一个reddit社区(如果你遇到复杂的问题,可以贴在这里,等别人来回答。)   

  

   discord社区:可以通过这个社区询问其他开发者关于Rust的问题。   

  

  Rust by example(https://doc . Rust-lang . org/Rust-by-example/index . html):介绍了一些例子,可以作为入门的首选书籍。   

  

     

  

  入门   

  

  参考Rust(https://www.rust-lang.org/)网站上的说明,使用rustup即可。   

  

  如果你想创建一个新的项目,运行cargo init dir(如果它在一个空的目录中,你不需要指定dir)。   

  

  然后就可以从src/main.rs开始写了   

  

  与C类似,主程序都被包装在main中。区别在于它不接受任何参数,也不应该返回整数。这些函数应该使用名称空间std:env。   

  

  另外,我推荐使用CLion并安装Rust扩展。VSCode也有Rust扩展,但是相比之下效果很差。当然也可以使用其他JetBrains编辑器,但是CLion有一些其他编辑器没有的原生功能(比如调试)。拥有GitHub教育包的学生可以免费使用这个插件。   

  

     

  

  有趣的注意事项   

  

  一切都有范围。   

  

  不仅变量,嵌套的函数和用法也可以在函数和trait内部使用。这些是不能从外部访问的,不使用也不会出现在代码中。至少我是这么认为的。   

  

  必须遵循的命名方案   

  

  变量和函数/方法只能使用小写字母、数字和下划线,比如snake_case,但是数字不能放在开头。   

  

  结构(和其他类型)、枚举(包括枚举值)和trait(但不是它们的函数/方法)需要以大写字母开头,并且不能包含任何下划线。   

  

没有增量运算符

  

实际上有,你可以使用i += 1。与赋值相同,该表达式将返回赋值后的值(即,将 i 设置为 i + 1,然后返回 i)。

  

没有 i++(或者 ++i、i-- 和 --i),因为这些运算符有点混乱。

  

你确定如下操作的结果吗(尤其是在没有指定语言的情况下)?

  

问题在于,直到最近上述运算的实际行为还是未定义的,这意味着不同的编译器(甚至可能是同一个编译器的不同版本)可能会产生不同的行为。为了解决这个问题并提高代码的可读性(Rust非常重视可读性和冗长,甚至不惜多敲几次键盘),Rust仅支持 i += 1,几乎所有人都知道该表达式的意思是变量i加1,并返回最终结果。所以,你不必知道 i++ 实际上返回的是原始值(不是新值),而且还会加1。

  

此外,运算符重载会使用trait,但本文不打算详细讨论。

  

几乎所有的东西都是表达式

  

除了函数调用之外,还有 if、while、match 和 for 都是表达式。

  

你可以直接使用 if 来代替其他语言中常见的三元运算符:

  

循环会根据break的调用返回结果。你可以利用它,反复重试某个操作,直到成功。

  


  

  

变量

  

变量通过 let 声明,并且有作用域。类型是可选的,Rust 非常擅长推断类型(比 Typescript 更出色)。

  

上述变量定义了一个类型为usize的变量var(usize是一个32或64位的数字,具体取决于计算机架构)。

  

你可以重复声明变量。当重复声明某个变量时,之前声明的变量就会被删除(除非该变量被引用,在这种情况下只有引用会保留,而原始变量会被删除),而且变量的类型也会改变。

  

在默认情况下,变量是不可变的。如果你想修改它们,则需要在 let 之后加上关键字 mut。

  


  

  

函数

  

函数的行为几乎与JS一模一样,只不过它们并不是数据类型,而且语法上略有不同。

  

参数的指定与 Typescript 类似,即key: type。返回类型通过 -> 指定。

  

有趣的是,虽然 Rust 需要分号,但如果最后一个表达式后面的分号忘写了,它会被作为返回值(即使没有 return 关键字)。

  


  

  

If语句

  

if 语句的使用非常基本,不在此赘述。

  

有一点需要注意,如非必要,使用括号实际上是错误的。你可以利用括号指定执行顺序:

  

如前所述,if 也可以返回一个值,而该值可用于赋值、参数、返回或其他地方。

  

这里的花括号是必需的。

  


  

  

类型

  

Rust的类型有两种:基本数据类型(数字、str),结构(String)。

  

二者之间唯一的区别是,基本类型的初始化可以直接赋值,而复杂类型则需要某种构造函数。

  

堆与栈

  

我之前几乎不需要考虑堆与栈的问题。(据我所知,JS中的对象都存储在堆中,只有基本类型在栈中。)

  

堆:

  

● 速度慢

  

● 比较大

  

  

● 非常快

  

● 比较小

  

基本类型和基本的结构都存储在栈中。要在堆中存贮值,需要使用Box<T>。另外,Vec<T> 也可以将值保存到堆中。

  

如果你使用的内存较多,或者需要在结构中使用带有值的enum,则可能需要使用堆。

  

如果发生栈溢出,则说明你使用了过多的栈内存。对于一些较大的值,应该使用Box。

  

常见的基本类型

  

数字:

  

● i8、i16、i32、i64、i128:有符号整数,包括负数。数字表示值的比特数。

  

● u8、u16、u32、u64、u128:无符号整数,从零开始。它们的最大容量翻了一倍,因为有一个额外的比特可用(在有符号整数中用于表示符号)。数字表示值的比特数。

  

● f32 和 f64:浮点数。javascript 世界中常见的数字。

  

字符串:

  

● str:简单的UTF-8 字符串(所有 Rust 字符串都是 UTF-8。不能使用无效的 UTF-8 字符串,会引发异常或造成panic)。通常用作指针(即 &str)。

  

● String:一种更复杂的类型(严格来说不是基本类型),存储在堆中。

  

数组:

  

● T<> :具有固定长度的数组(如果使用 Option<T> 类型,则数组内包含的元素数量可以小于实际长度)。

  

元组

  

元组可用于存储不同类型的多个值(从本质上来说就是可以容纳不同类型且大小固定的数组)。

  

与数组不同,元组可通过点(.)直接访问,例如 tuple.0 表示获取第一项,而 tuples 没有.len() 之类的方法。

  

有一个很有意思的小技巧,你可以通过()(空元组)返回“void”。既没有 return 语句,也不会返回值的函数会返回()。

  

常见结构

  

Option<T>

  

● 这是一个枚举,值为Some(T) 或 None。(我们稍后再讨论enum,Rust中的枚举与其他语言略有不同。)

  

● 如果想获取该值,你可以使用 match,就像使用其他枚举一样,或者使用 .unwrap() (如果值为None,则会导致panic)。

  

Result<T, E>

  

● 这个结构与 Option 类似,但常用于处理错误(通常由 IO 方法返回)。

  

● 它的值是 Ok(T) 或 Err(E)。

  

● 如果想获取该值,你可以使用match 块或 unwrap()。

  

● 为了方便使用,当函数返回 Result<T, E> 时,可以在返回值为 Result<T, E>(其中E必须为兼容的类型)的方法调用之后使用 ? 来返回错误E(类似于使用.unwrap(),但当函数出错时不会造成panic)。

  

Vec<T>

  

● 向量是可增长的数组,存储在堆上。

  

● 向量支持 .push()、.pop() 等常用操作。详情参见Rust文档。

  

Box<T>

  

● 在堆上存储T。可用于在结构中使用enum,或者用于释放栈空间。

  

定义结构

  

结构类似于对象,但它们的大小是静态的。

  

结构可以通过如下几种方式定义。

  

● 使用Tuple作为声明(类似于元组的别名)

  

● 使用对象表示法(类似于声明类或对象)

  

● 使用struct作为别名

  

这种方法的适用情况为:你试图创建一个enum,而其值可能是正在定义的结构,而该结构中又要(直接或间接)引用该enum。

  

我在为自己的shell创建抽象语法树时,就遇到了这个问题。

  

要创建结构的实例,需要使用下述写法(类似于C#中定义数组):

  

  

定义enum

  


  

下面是示例:

  

可以为enum指定数值(例如序列化或反序列化数值的情况):

  

更强大的写法如下:

  

你可以用match提取出值。

  


  

  

Match

  


  

match是Rust最强大的功能之一。

  

Match是更强大的switch语句。使用方法与普通的swtich语句一样,除了一点:它必须覆盖所有可能的情况。

  

也可以match范围:

  

也可以什么都不做:

  

可以使用match安全地unwrap Result<T, E>和Option<T>,以及从其他enum中获取值:

  

如果你不使用i(或其他值),Rust会发出警告。你可以使用_来代替。

  

match也是表达式:

  

你可以看看Option的文档(或通过IDE的自动补齐,看看都有哪些可以使用的trait或方法)。

  

你也许注意到了,你可以使用.unwrap_or(val)来代替上述代码(上述match等价于.unwrap_or(0))。

  


  

  

Loop

  


  

loop循环是最简单的循环。只需要使用loop即可。

  

该代码会一直运行,直到遇到break(或return,return也会同时返回父函数)。

  


  

  

for

  


  

for循环是最简单易用的循环。它比传统的for循环更容易使用。

  


  

  

while

  


  

很简单的循环。与其他语言不同,Rust没有do...while,只有最基础的while。

  

语法与if一样,只不过内容会循环执行。

  


  

  

打印输出

  


  

打印输出可以使用 print! 和 println!。

  

!表示这是一个宏(即可以扩展成其他代码的快捷方式),但你不需要过多考虑。另一个常用的宏是 vec!<> ,它能利用数组创建 Vec<T> (使用 <> 内的值)。

  

这些宏都有一个简单的模板系统。

  

● 输出一行使用 println!()。

  

● 输出一个静态字符串使用 print!("something")。println!中ln的意思是行,也就是说它会添加换行符号(\n)。console.log会默认添加换行。

  

● 要输出一个实现了Display trait的值(绝大多数基本类型都实现了),可以使用 print!("{variable}")。

  

● 要输出一个实现了Debug trait的值(可以从Display继承),使用 print!("{variable:?}")。

  

● 要输出更复杂的实现了Display trait的内容,使用 print!("{}", variable)。

  

● 要输出更复杂的实现了Debug trait的内容,使用 print!("{:?}", variable)。

  


  

  

Trait

  


  

Trait是Rust中最难理解的概念之一,也是最强大的概念之一。

  

Rust没有采用基于继承的系统(面向对象的继承,或JavaScript基于原型的继承),而是采用了鸭子类型(即,如果一个东西像鸭子一样叫,那么它就是鸭子)。

  

每个类型都有且只有一个“默认”(或匿名)trait,只能在与该类型同一个模块中实现。通常都是该类型独有的方法。

  

其他的都叫trait。例如:

  

首先,我们定义了trait(在面向对象语言中叫做接口,但它只包含方法或函数)。然后为给定的类型(上例中为Dog)实现trait。

  

一些trait可以自动实现。常见的例子就是Display和Debug trait。这些trait要求,结构中使用的类型必须要相应地实现Display或Debug。

  

作用域

  

Trait有作用域,而且与它实现的类型的作用域是独立的。也就是说,你可以使用一个类型,但无法使用一个trait的实现(例如,如果这个实现来自另外一个库,而不是来自该类型本身)。你可以use这个实现。

  

self

  

trait中的self指向它实现的类型。&self是指向 self: &Self 的别名,其中Self表示该类型(上例中的 self: &Dog)。self也是self: Self的别名,但两者的区别就是后者会移动变量(即消耗该变量,该变量就无法从外部访问了)。

  

当函数定义不以self、&self或&mut self开始时(&mut self相当于带有可改变引用的 &self),就是一个静态方法。Trait依然可以像任何方法一样定义并实现静态方法。常见的一个静态方法是new,用于创建类型或结构的实例:

  

  

指针

  


  

指针实际上非常易懂,尽管它来自其他更高级的语言。我经常会用错。

  

&A指向A,使用时只需要确保A存在,即可保证&A存在,因为我们不应该让指针指向不存在的对象。

  

Rust会在编译时进行静态检查,确保不会出现上述情况。它会自动释放超出作用域的变量,并且不允许指针的存活超过变量。另一个安全保证是,只能有一个可改变的指针。

  

也就是说下述代码是错误的:

  

我们只需要保证原始变量在指针的作用域中一直存在即可。

  

在结构中使用指针会有点问题,因为编译器不喜欢这种做法(因为结构的寿命通常比原始变量更长)。我通常会采用所有权转移或克隆(.clone(),Clone trait的一部分,可以被derived)。

  

有时候,一些函数要求只能用指针,不能用所有权转移。这时,只需在值的前面加上 & (或 &mut)即可。

  

此外,还有双重、三重等指针,但很少见,而且一般来说只会更难处理。

  

你也不需要考虑释放变量的问题,Rust会在超出作用域时自动释放。

  


  

  

命名空间

  


  

使用全名就无需导入。导入只不过是别名。

  

选择多个“命名空间”可以使用{},如:

  

也可以重复使用use:

  

还有一点,你也可以在函数内使用use。这样,如果代码没有被执行,库就不会被导入(即,如果函数没有在代码路径中出现,例如,use了一个测试用的库,而use只写在了测试用例中,那么在正常构建时就不会导入该库)。

  

但我不推荐在正常的代码路径中这样写,应该使用全局的导入。

  


  

  

可见性

  


  

讨论完命名空间之后,我们来讨论一下可见性。

  

本质上,默认情况下任何东西都是私有的,只能被它所在的文件访问。

  

● trait及其方法

  

● 结构及其成员

  

● enum(成员继承enum的可见性,这是合理的,参见Match)

  

● 函数

  

● trait的实现依赖于trait和实现该trait的结构,即,只有两者都是公有的,该实现才是公有的。

  

要设置为公有(即可以从外部访问),需要使用关键字pub:

  

  

使用多个文件

  


  

有时候我会想念 require("./fire")。

  

要想“导入”一个文件,要使用 mod指令。通过cargo下载的crate会自动导入。

  

main.rs

  

my.rs

  

你也可以使用pub mob重新导出一个文件。绝大多数已有的Rust代码都会对支持文件夹采用下列操作:

  

main.rs

  

my/mod.rs - mod.rs这个名字是特殊的,类似于index.js

  

my/file.rs

  

关于println!,参见“打印输出”。

  


  

  

编写文档

  


  

编写文档只需使用三个斜线 ///。一些IDE会采用不同的高亮方式显示。

  

类似于JSDoc,只不过其类型不会显式标注,因为代码中已经写了类型。

  

原文地址:https://danbulant.eu/posts/rust-basics

  

  

《新程序员001-004》全面上市,对话世界级大师,报道中国IT行业创新创造

  

  

  

<

相关文章