单次猜测 获取用户的命令行输入
1 2 3 4 5 6 7 8 use std::io;fn main () { println! ("guess a number" ); let mut guess = String ::new (); io::stdin ().read_line (&mut guess).expect ("error reading line" ); println! ("your number is {}" , guess); }
库的引用 默认情况下 Rust 会将 prelude
模块(预导入模块)的内容导入到每个程序的作用域中,如果要使用的库不位于 prelude
模块中,则需要通过 use
关键字显示的导入。在此示例中,获取用户命令行输入的库为 io 库,而 io 库位于 Rust 标准库 std 中,导入方法为 use std::io;
变量的不可变 默认情况下,Rust 的变量均为不可变的(immutable)。
1 2 3 4 5 fn main () { let foo = 1 ; foo = 2 ; println! ("foo is {}" , foo) }
1 2 3 4 5 6 7 8 9 10 11 $ cargo run error[E0384]: cannot assign twice to immutable variable `foo` --> src/main.rs:3:5 | 2 | let foo = 1; | --- | | | first assignment to `foo` | help: consider making this binding mutable: `mut foo` 3 | foo = 2; | ^^^^^^^ cannot assign twice to immutable variable
如果要声明一个可变的变量,那么需要在变量前加上 mut
关键字
1 2 3 4 5 fn main () { let mut foo = 1 ; foo = 2 ; println! ("bar is {}" , foo) }
需要注意的是,引用默认也是不可变的,而 read_line()
方法会根据用户的输入修改传入的变量,因此,也要对入参声明可变。
关联函数 String::new()
会返回字符串的一个新的实例,内部是 utf-8 编码的,中间的两个冒号表示 new()
是 String
这个类型的关联函数,关联函数表示针对这个类型本身来实现的,不是针对这个类型的某个特定示例来实现的,也就是 new()
不会作用于 guess 实例,类似于 Golang 中的结构体方法。
同理,io::stdin()
会返回一个 Stdin 类型的句柄。
Result Rust 中有很多种 Result 类型,即有通用泛型的 Result,也有针对特定类型的 Result,例如 io::Result
,Result 实际上就是一个枚举类型,包括两个值,一个是 Ok
一个是 Err
,expect()
方法用作错误判断,如果返回的值为 Err,那么会中断程序并将入参输出。
占位符 区别于 Golang,println!()
中如果想输出变量,那么必须要有占位符,即 {}
。
神秘数字 引入第三方 rand 包,实现随机数的生成
1 2 3 4 5 6 7 8 9 10 11 12 use std::io;use rand::Rng;fn main () { let secret_number = rand::thread_rng ().gen_range (1 , 101 ); println! ("secret_number is {}" , secret_number); println! ("guess a number" ) let mut guess = String ::new (); io::stdin ().read_line (&mut guess).expect ("error reading line" ); println! ("your number is {}" , guess); }
第三方依赖包 在 Cargo.toml
的 dependencies
新增 package = version
信息为项目新增第三方依赖包。
^
表示任何一个与指定版本 api 兼容的库均可以,并且该标识为默认。
Cargo.toml
1 2 3 4 5 6 7 8 9 [package] name = "hello-world" version = "0.1.0" edition = "2021" [dependencies] rand = "^0.3.14"
首次构建时,cargo 会更新源的 index,根据 Cargo.toml
的内容下载依赖,将下载的依赖信息写入 Cargo.lock
中,并完成源码和依赖的构建,当后续源码或者发生变化,则仅会重新构建变化部分。
1 2 3 4 5 6 7 8 9 10 $ cargo build Updating crates.io index Downloaded rand v0.3.23 Downloaded rand v0.4.6 Downloaded 2 crates (87.7 KB) in 0.77s Compiling libc v0.2.112 Compiling rand v0.4.6 Compiling rand v0.3.23 Compiling hello-world v0.1.0 (/Users/shenxianghong/Documents/Project/Rustaceans/hello-world) Finished dev [unoptimized + debuginfo] target(s) in 5.06s
Cargo.lock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <skip> [[package]] name = "libc" version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "rand" version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" dependencies = [ "libc" , "rand 0.4.6" , ]
当对依赖跨大版本版本更新时,需要手动修改 Cargo.toml
,除了可以通过重新构建的方式,还可以通过 cargo update
重新维护依赖关系。
rand 包升级至 0.7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 $ cargo update Updating crates.io index Removing cfg-if v1.0.0 Adding fuchsia-cprng v0.1.1 Removing getrandom v0.1.16 Removing ppv-lite86 v0.2.16 Removing rand v0.7.3 Adding rand v0.3.23 Adding rand v0.4.6 Removing rand_chacha v0.2.2 Removing rand_core v0.5.1 Adding rand_core v0.3.1 Adding rand_core v0.4.2 Removing rand_hc v0.2.0 Adding rdrand v0.4.0 Removing wasi v0.9.0+wasi-snapshot-preview1 Adding winapi v0.3.9 Adding winapi-i686-pc-windows-gnu v0.4.0 Adding winapi-x86_64-pc-windows-gnu v0.4.0 shenxianghong@Corgi hello-world % cargo update Updating crates.io index Adding cfg-if v1.0.0 Removing fuchsia-cprng v0.1.1 Adding getrandom v0.1.16 Adding ppv-lite86 v0.2.16 Removing rand v0.3.23 Removing rand v0.4.6 Adding rand v0.7.3 Adding rand_chacha v0.2.2 Removing rand_core v0.3.1 Removing rand_core v0.4.2 Adding rand_core v0.5.1 Adding rand_hc v0.2.0 Removing rdrand v0.4.0 Adding wasi v0.9.0+wasi-snapshot-preview1 Removing winapi v0.3.9 Removing winapi-i686-pc-windows-gnu v0.4.0 Removing winapi-x86_64-pc-windows-gnu v0.4.0
除此之外,cargo update
还可以用于依赖包的小版本升级:当执行升级时,Cargo 会忽略 Cargo.lock,根据 Cargo.toml 的包版本信息,升级到最新的小版本,而不会突破大版本,升级之后 Cargo.lock 会更新,而 Cargo.toml 保持不变,也就是基于语义化的版本升级。
Trait Trait 可以理解成 Golang 中的接口,定义了许多方法。rand::Rng
就是一个 Trait,定义了一组随机数生成器所需要的方法。rand::thread_rng()
这个函数返回是一个 ThreadRng
类型,本质上是一个运行在本地线程空间中,通过操作系统获取随机数种子的随机数生成器,而 gen_range()
就是 Trait 的方法之一。
不导入 trait,但是使用 trait 方法,会引起报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ cargo run error[E0599]: no method named `gen_range` found for struct `ThreadRng` in the current scope --> src/main.rs:6:44 | 6 | let secret_number = rand::thread_rng().gen_range(1, 101); | ^^^^^^^^^ method not found in `ThreadRng` | ::: /Users/shenxianghong/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.4.6/src/lib.rs:524:8 | 524 | fn gen_range<T: PartialOrd + SampleRange>(&mut self, low: T, high: T) -> T where Self: Sized { | --------- the method is available for `ThreadRng` here | = help: items from traits can only be used if the trait is in scope help: the following trait is implemented but not in scope; perhaps add a `use` for it: | 1 | use rand::Rng; |
比较猜测数字与神秘数字 猜测数字为 string 类型,神秘数字为 int 类型,转换后进行大小比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 use std::io;use std::cmp::Ordering;use rand::Rng;fn main () { let secret_number = rand::thread_rng ().gen_range (1 , 101 ); println! ("secret_number is {}" , secret_number); println! ("guess a number" ); let mut guess = String ::new (); io::stdin ().read_line (&mut guess).expect ("error reading line" ); let guess : u32 = guess.trim ().parse ().expect ("error parsing guess number" ); println! ("your number is {}" , guess); match guess.cmp (&secret_number) { Ordering::Less => println! ("Too small" ), Ordering::Greater => println! ("Too big" ), Ordering::Equal => println! ("You win" ), } }
枚举 std::cmp::Ordering
是一个枚举类型,包含三个值,分别是 Ordering::Less
、Ordering::Greater
和 Ordering::Equal
,枚举类型的使用也需要使用双冒号格式。
match cmp
方法返回的是 Ordering 类型,根据不同的分支(arm)判断匹配模式,从而执行不同的逻辑,即 =>
之后的逻辑,类似于 Golang 中的 switch case 用法。
Shadow Rust 中允许使用同名的变量来覆盖之前的变量,区别于 Golang,不仅可以用于覆盖值,可以类型也可以不一样。一般用于在不额外声明变量的场景下,进行类型转换。
类型 Rust 是强类型语言,并且具备类型推断的能力,gen_range(1, 101)
会返回 1 到 100 之间的随机整数,Rust 中涵盖此范围的类型很多,比如 i32、u32、i64 等等,如果未做进一步的声明,Rust 默认其为 i32。
可以注意到,变量 guess 被转换成了 u32 类型,而接下来还对变量 guess 和 secret_number 进行了 match 比较,因此 Rust 也会将变量 secret_number 设置为 u32 类型编译,因此,如果没有 match 比较,则 Rust 会将其默认为 i32。
多次猜测 增加死循环,直至猜对退出;增加错误处理,完善健壮性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 use std::io;use std::cmp::Ordering;use rand::Rng;fn main () { let secret_number = rand::thread_rng ().gen_range (1 , 101 ); loop { println! ("guess a number" ); let mut guess = String ::new (); io::stdin ().read_line (&mut guess).expect ("error reading line" ); let guess : u32 = match guess.trim ().parse () { Ok (num) => num, Err (_) => continue }; println! ("your number is {}" , guess); match guess.cmp (&secret_number) { Ordering::Less => println! ("Too small" ), Ordering::Greater => println! ("Too big" ), Ordering::Equal => { println! ("You win" ); break }, } } }
死循环 Rust 中的死循环使用 loop
关键字,退出使用 break
关键字,继续使用 continue
关键字。
错误处理 Rust 中常用的错误处理方式是基于 match 模式,例如 parse() 方法返回 Result 类型,该类型包括两个枚举值。其中 Ok(num)
表示猜测数字解析成功,num 为解析之后的数字,通过 => 赋值给 guess。同理,Err(_)
表示解析失败,_
为错误信息,下划线表示忽略。