ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Rust之路(1)

2020-10-09 15:03:16  阅读:325  来源: 互联网

标签:cargo 函数 i32 之路 main Rust gcd


【未经书面许可,严禁转载】-- 2020-10-09 --

 

正式开始Rust学习之路了!

思而不学则罔,学而不思则殆。边学边练才能快速上手,让我们先来个Hello World!

但前提是有Rust环境啊!

Rust是跨平台的语言,而且无论在Windows还是在Linux、macOS上安装都比较简单。打开官网的安装指导页面:

https://www.rust-lang.org/tools/install

网站会根据当前使用的系统给予响应的安装指导。Windows系统就是下载exe安装程序,下载后安装即可;macOS和Linux使用一道curl命令安装。

安装过程是在命令行模式下进行,当有如下提示时输入1,即可:

Current installation options:
   default host triple: x86_64-apple-darwin
   default toolchain: stable (default)
   profile: default
    modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation 
>

 

[题内话]

  • Rust安装目录都是默认在用户的家目录。我在Windows上安装的时候按照说明改了系统变量也没修改成功安装目录,但是安装后可以把系统盘的用户目录里安装后的文件夹(有两个文件夹:.cargo和.rustup,注意文件夹的第一个字符是一个点)拷贝到别的盘,然后修改系统变量指向新的位置(必要的步骤)。更改安装位置是因为后期随着Rust安装的库增多,.cargo文件夹会变的很大,有系统盘吃紧的危险。
  • Windows系统需要同时安装Visual Studio C++ Build tools,如果安装过visual Studio2013以上可以忽略,否则请至https://visualstudio.microsoft.com/visual-cpp-build-tools/下载安装。
  • macOS安装后编译Rust程序出现linking with cc failed错误,并且有xcrun error字样的话,安装苹果家的开发环境Xcode即可(APP Store搜索安装)。
  • 由于访问Rust官网下载和安装较慢,可以使用国内镜像服务器(以清华大学TUNA镜像站为例,也可自行选择其他镜像站):在安装前,Windows系统在系统变量里增加RUSTUP_DIST_SERVER,变量值为https://mirrors.tuna.tsinghua.edu.cn/rustup;macOS和Linux执行命令:echo 'export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup' >> ~/.bash_profile,执行此命令后可能需要重新打开终端输入安装命令才能生效。

 

[题外话]

  • 当今时代,跨平台已成为硬通货。Rust是跨平台的,这意味着,使用率最高的三大系统Windows、macOS、Linux都可以安装使用,源码不用改动即可以从Windows上编写,然后把源码拷贝到Linux上,重新编译即可使用(当然涉及到依赖系统API的除外)。
  • 最近的Windows 10更新版本内置了Linux系统,而且不是用虚拟化技术实现,而是直接嵌入式。微软积极拥抱Linux内核,说明可能十年八年以后,桌面操作系统的格局会有很大变化,所以,学习跨平台的技术,能使用更持久。

因为安装很简单,不多赘述。

关于用什么IDE(写代码、编译、调试的环境),其实有很多选择。国内更喜欢微软的Visual Studio Code,国外用Atom的也很多,还有jetbrains公司的IntelliJ Rust + Clion(Clion是IDE,Intellij Rust是插件,貌似免费的社区版不带调试功能)。因为配置环境的教程很多,我也不多说了,各个电脑有各个电脑的情况,出现了不能调试或其他问题的解决方法也不能一概而论。

安装完毕,先跑个分?(Oh, NO!我们不是雷总)

按照国际编程惯例,先Hello World一下?

Emmm,我们应该更有追求一点,写个稍微有意义点的Demo。

[题内话]

如果需要Rust安装或IDE安装详细过程,可以留言,我再考虑完善。

 

[题外话]

某些方面真的能体现出Rust是年轻的编程语言:一方面:没有统领性的IDE,几大IDE厂商还在完善和竞争的阶段,各IDE都有优缺点,配置也是各种野路子;另一方面,Rust主要还是开发黑窗口应用,GUI编程还不成熟。不过好消息是据说Rust2021版会集成有官方GUI框架。

 

开始进入Rust的世界!

Rust源码文件的后缀名是rs,主文件一般命名为main.rs。下面我们新建一个Rust源码文件main.rs,用IDE的编辑器,甚至可以用记事本打开此文件,输入:

 1 //Rust程序1.1
 2 //程序入口函数
 3 fn main() {
 4     let a = 4;
 5     let b = 6;
 6     let num = gcd(a,b);
 7     println!("{}和{}的最大公约数是{}", a, b, num);
 8 }
 9 //函数,输入两个数字,输出二者的最大公约数
10 fn gcd(mut n: i32, mut m: i32) -> i32 {
11     assert!(n != 0 && m != 0);
12     while m != 0 {
13         if m < n {
14             let t = m;
15             m = n;
16             n = t;
17         }
18         m = m % n;
19     }
20     n
21 }

 

这个小程序使用欧几里得算法计算两个数字的最大公约数。将main.rs文件保存,打开命令提示符(windows系统)或终端窗口(macOS、Linux),使用cd命令切换到文件保存目录,然后输入编译命令:

 

rustc main.rs

 

回车,main.rs就会进行编译,编译完成在同一目录就会有编译成功的程序main.exe(Windows系统)或main(macOS、Linux)。

然后输入:

main

回车,命令就会执行。执行结果输出为:

4和6的最大公约数是2

 

然后程序自动退出。

此段程序代码的信息量很大,不要着急,我们一句一句来解释,解释通了这个小程序,就相当于望见了Rust大门的门楣了!

程序1.1的第3行(正式代码的第1行):

fn main() {

 

这是Rust的函数定义,关键字fn是function(函数)的缩写,音标读作[fʌn]。用fn来表示此处正在定义一个函数。main是函数名称,可以使用字母、数字、下划线,但不能用数字开头。前面说过,Rust的入口函数必须是main,即,一个Rust可执行程序的源代码必须有且只有一个main函数,程序就是从main函数开始的。函数名后跟一对小括号,如果函数有参数,可以放到小括号中间(此处main函数是没有参数的,所以小括号内是空的)。此行最后是左大括号,预示着后面是main函数的函数体了。

第4、5行:

let a = 4;
let b = 6;

 各声明了一个变量并给变量赋值,let是变量声明的关键字,后面跟的是变量名、赋值操作符=以及变量的值(数字4和6)。

变量名的规则和函数名一样,使用字母、数字和下划线。所赋的值4和6在Rust中默认是i32型,即32位带符号整数型,带符号的意思是有正值也有负值,下一节讲数据类型。

Rust的变量声明规则有:

  • 声明变量并同时赋值语句中,如果能从所赋的值推断出类型,那么就不需要写变量的类型。如果无法推断,或者是只有变量声明,没有赋值(形如 let a;),则必须加变量的类型,语法是let 变量名:类型(写作let a:i32);
  • 赋值可以用字面量,也可以用表达式(例如后面学习的match、if else语句);
  • 关于推断,Rust是非常高阶的。会分析整个代码,将没标明类型的变量做出推断。如发现无法推断又没标明类型的,就会发出异常编译错误。
  • 默认情况下,一旦一个变量被初始化,它的值就不能改变,但是在变量名之前加上mut关键字(发音为[mjuːt],是mutable的缩写)可以声明可变变量。在实践中,大多数变量都没有声明为可变。使用mut强调变量是否可变在阅读代码时非常有用。

[题内话]

Rust的类型推断不限于当前的赋值语句,而是对所有代码综合分析,这一点不像其他语言。例如:

将程序1.1中的gcd函数签名修改为:fn gcd(mut n: u64, mut m:u64) -> u64

即函数的输入参数和返回值都修改为64位无符号整型,其他代码都不变。那a、b应该推断为什么类型呢?

按常规理论,编译器首先读取了let a=4;let b=6;代码以后,应该把a和b推断成最常规的、数字4和6的类型:i32型。然而事实并非如此!

事实是:对整段程序代码进行分析,后面会将a、b当做实参传入gcd函数,而gcd的形参类型我们修改为u64型了,所以,a和b最好的安排就是使用u64型,这样传入gcd函数的时候,值类型才正确。这一点是可以证明的。

Rust的绝大多数类型的值都有一个type_id()方法,用于返回类型标识,其值是std::any::TypeId类型,这个类型用一串数字标识各种类型,每种类型都不一样。另外std::any::TypeId类型还有一个泛型静态函数TypeId::of::<T>(),尖括号<>内的T是某种类型名,例如i32,String等等,返回值是这种类型的类型标识。

我们就用这个函数验证一下上面的结论:在main函数的最后部分:

println!("{}和{}的最大公约数是{}", a, b, num);

//加上如下一段代码

         println!("a的TypeId是:{:?}", a.type_id());

         println!("b的TypeId是:{:?}", b.type_id());

         println!("num的TypeId是:{:?}", num.type_id());

         println!("i32类型的TypeId是:{:?}", TypeId::of::<i32>());

         println!("u64类型的TypeId是:{:?}", TypeId::of::<u64>());

         //以上为加上的代码段

}

运行后会打印出a、b、num、i32类型、u64类型的TypeId,可以看出a、b、num都和u64类型的TypeId相同,而不是i32的TypeId。

 

上面有些概念不理解没问题,后面会陆续讲到。

第6行:

let num = gcd(a,b);

前半部分let num=,和上面讲的一样,是变量声明和赋值。后半部分gcd(a,b),是函数调用的格式,即程序运行到此处,会进入到函数gcd内执行,并把a、b两个变量分别赋给gcd函数的两个形参。gcd函数执行完毕,获取执行的结果,然后赋值给num变量。

第7行:

println!("{}和{}的最大公约数是{}", a, b, num);

 Rust中,一个标识符加一个叹号,是宏的使用格式,println是宏名。在编译的时候,宏会被一段代码替换,代码是println宏定义来定义的。println宏接收一个模板字符串,将第2个及以后的参数的格式化版本填充到模板字符串。在本例中,a的值替换掉字符串中第一个{},b替换第二个{},num替换第三个{}。形成了“4和6的最大公约数是2”字符串。然后输出到标准输出,也就是屏幕。

第10行:

fn gcd(mut n: i32, mut m: i32) -> i32 {

 声明了gcd函数,与上面的main函数不同,gcd函数的小括号内有两个参数。因为函数体内需要改变m和n的值,所以参数用mut标记为可变,而且函数的形参需要声明类型,格式为:变量名:类型。i32表示为32位带符号整型,而u32表示32位无符号整型,u是unsigned的简写。

函数声明的后半部分 ->i32,表示的是函数的返回值类型也是i32。

第11-21行:

    assert!(n != 0 && m != 0);
    while m != 0 {
        if m < n {
            let t = m;
            m = n;
            n = t;
        }
        m = m % n;
    }
    n
}

 这一段是函数gcd的函数体和结尾大括号。函数体以对assert宏的调用开始,验证两个参数都不是零。叹号!将其标记为宏调用,而不是函数调用。像C和C++中的断言宏一样,Rust的断言检查它的参数是否为真,如果不是,则输出一条提示消息提示失败及失败的代码位置,并终止程序。Rust的这种突然终止称为panic,大多数中文译文中直译为恐慌,我觉得翻译成异常更好,虽然这样很不Rust。C和C++程序可以跳过断言,但Rust总是检查断言,而不管程序是以什么方式编译的。另外还有一个debug_assert!宏,只有在debug模式下才检查断言,而已优化代码的方式进行release编译时不检查。

函数的核心是包含if语句和赋值的while循环,逻辑很简单,可以了解一下最大公约数的欧几里得计算法。先确认m大于或等于n,如果m比n小,则交换值,然后求m除以n的余数,赋值给m,直到m=0,则此时n就是原来两个数的最大公约数。

所以,函数gcd应该返回n的值,注意函数体最后一行,只有一个n,而且n后面没有分号作为结束。这是Rust的函数返回值的通常写法,函数最后一行,写一个表达式,表达式的值就是函数的返回值。但是如果在函数体代码中间返回时,需要写return xxx;并且需要有分号结束。例如:

fn xxx(a:i32, b:i32)->i32{
    if a>b{
        return a;
    }
    else{
        b
}
}

 

[题外话]

表达式和语句:在Rust中,一句代码后面没有分号结束,就是表达式,是有值的;有分号结束,就是语句,没有返回值(实际上返回值是个(),()是一种特殊的元组类型,意为空,所以认为是没有返回值)。

 

Rust不需要在if后的条件表达式用括号括起来,但函数体需要用大括号括起来。后面会用到的whle循环和match匹配,也遵循同样的规定。

程序1.1由两个函数组成:main()和gcd(),main是程序入口。在大多数语言中,main函数体内调用gcd函数,那必须在main函数之前定义gcd函数,最起码像C++那样有个前向声明。但是Rust不在意两个函数的顺序,只要有,编译器就能知道。

 

Cargo包管理器

我们再看一下在Rust中的大杀器Cargo工具,cargo是Rust的编译管理器、包管理器和通用工具。可以使用Cargo启动一个新项目,构建和运行程序,以及管理代码所依赖的任何外部库。换句话说,用Cargo命令可以创建一个项目,然后可以用IDE编写代码,代码编写完成后,Cargo可以执行编译、运行、测试(当然是Cargo调用了其他的命令来执行这些动作)。而且程序中使用到的依赖包也能靠Cargo在编译阶段自动在线下载和编译,你只需要在配置文件中输入依赖包的名称和版本需求。

例如一个项目开发的过程:

事项 操作/命令 备注
创建一个项目文件夹

创建一个可执行程序:

cargo new --bin project01

创建一个库项目:

cargo new --lib project01

project01是项目名称,bin或lib选项前是两个减号--。命令执行完毕,会创建一个项目文件夹,里面包括配置文件(toml后缀名),src文件夹(文件夹内有main.rs或lib.rs)
编写程序代码 在IDE中编写代码,部署多个rs文件 第三方依赖包需要在toml配置文件的[dependencies]段添加
编译 cargo build 下载、编译依赖包,编译程序
测试 cargo test 此操作会测试代码中使用#[test]标记的函数,其他函数都会忽略
运行 cargo run 如果想直接运行,可忽略cargo build操作,直接run

这些是cargo的基本使用,此命令还有很多用法,可运行cargo –h查看帮助。

让我们把上面的程序改造成用cargo来管理。

首先在适当的位置新建一个项目,比如在D:\Programs(Windows)或~/Programs(macOS、Linux)。

Windows

 

macOS、Linux

 

 

用IDE或记事本打开prog_gcd目录下的main.rs文件,你会发现里面有个HelloWorld程序,所以本例没有展示HelloWorld的写法,因为Rust自带了!把main.rs的内容清空,然后输程序1.1的代码,保存。

再回到命令提示符/终端窗口,运行cargo run,cargo就会自动编译,然后运行编译后的程序,最终得到想要的结果!

是不是So easy!

在Rust开发中,一定要用cargo,它做了很多的幕后工作,为我们节省时间和精力。在包含多文件的项目中,cargo的文件组织能力才是最有用的。

cargo新建项目的时候,新建了项目文件夹prog_gcd,进入项目文件夹,新建了配置文件(项目名).toml,打开后内容为:

1 [package]
2 name = "prog_gcd"  #修改此处,可重命名生成的程序名
3 version = "0.1.0" #版本号
4 authors = ["sumyuan"] #作者和版权信息
5 edition = "2018" #Rust版本,目前只能写2015和2018两个版本之一
6 #下面是一段自动生成的说明信息
7 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 
9 [dependencies] #这下面可以添加程序的依赖包

 

toml文件中注释写在#后。我们这个简单程序暂时没有依赖第三方包,所以[dependencies]节的内容为空。

 src文件夹内存放rs源文件。在经过cargo build编译,或cargo run编译并运行后,项目文件夹内会产生target文件夹,在taget文件夹内,有debug文件夹,它用于存放编译后的程序,如果在编译时加了release参数,命令如:

cargo build –release  或  cargo run –release

target文件夹内会产生release文件夹,它用于存放release模式编译后的程序。

文件夹内其他的文件我们暂且不管。

题内话:

debug模式和release模式,是绝大多数编译器的两种编译方式。debug模式不对代码做任何优化,并且可以设置断点,附加了很多调试所用到的状态,用于自行调试;release模式对代码会做优化,优化程度也可用参数控制,使得生成的程序或库体积小、运行速度快,但是不能断点调试,用于最终发布。

需要说的内容还有个坑要填。cargo test用于测试程序,前提是代码中设置了测试函数。

我们在程序1.1的最后,加入一个函数,代码如下:

//Rust程序1.1
//前面内容省略......
#[test]
fn test_gcd() {
    let num = gcd(120, 90);
    println!("{}和{}的最大公约数是{}", 120, 90, num);
}

在函数test_gcd()的上方,加上#[test],cargo进行测试的时候会知道这个函数是测试函数。在执行cargo test时,不是去找main函数,反而会查找代码中所有有此标记的函数,依次执行。执行结果:

D:\Programs\prog_gcd>cargo test
   Compiling prog_gcd v0.1.0 (D:\Programs\prog_gcd)
    Finished test [unoptimized + debuginfo] target(s) in 1.59s
     Running target\debug\deps\prog_gcd-d88e6d06203d2866.exe

running 1 test
test test_gcd ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

结果显示测试通过,说明我们写的test_gcd以及调用的gcd函数是没问题的。

 

【总结】

在我们写的第一个小程序中,包含了Rust的诸多语法:

变量的声明和赋值,以及可变性(let关键字,mut的用法);

简单的数字类型i32,u64;

函数的声明和定义的语法(fn关键字,返回值类型用->表明);

初步认识宏的使用(叹号!的使用);

类型推断(整体推断);

函数传参(变量或值传入函数);

另外,讨论了cargo的常用命令(new\build\run\test)和程序测试(#[test])。

知道了这些,就算跨入了正式的Rust学习之路了,后面我们陆续进行各种语法的学习和演练。

 

标签:cargo,函数,i32,之路,main,Rust,gcd
来源: https://www.cnblogs.com/sumyuan/p/13785785.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有