使用cargo new命令创建新的工程,先来使用cargo help new命令看一下new的参数:
cargo.exe-new
Create a new cargo package at <path>
USAGE:
cargo.exe new [OPTIONS] <path>
OPTIONS:
-q, --quiet No output printed to stdout
--registry <REGISTRY> Registry to use
--vcs <VCS> Initialize a new repository for the given version control system (git, hg, pijul, or
fossil) or do not initialize any version control at all (none), overriding a global
configuration. [possible values: git, hg, pijul, fossil, none]
--bin Use a binary (application) template [default]
--lib Use a library template
--edition <YEAR> Edition to set for the crate generated [possible values: 2015, 2018]
--name <NAME> Set the resulting package name, defaults to the directory name
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
ARGS:
<path>
可以看到cargo支持版本控制,除了git,其它的都没听说过,而且不支持svn,难道svn已经被时代无情的抛弃了?
cargo还为工程提供了两种的模板,可以通过参数来选择模板。
cargo new --bin guessing
Created binary (application) `guessing` package
执行完成后,在当前目录下生成了guessing目录,目录结构如下:
hellorust
├─ .git
├─ .gitignore
├─ Cargo.toml
└─ src
└─ main.rs
cargo默认使用git作为版本控制器,在生成代码文件的同时,还生成了一个Cargo.toml。
Cargo.toml文件是该工程的说明文档,内容如下:
[package]
name = "guessing"
version = "0.1.0"
authors = ["zhangmh <zhangmh@sdysit.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
不知道我的信息是从哪里读取的。
根据选择的模板,main.rs中的原始代码也不相同,可执行程序的main.rs内容如下:
fn main() {
println!("Hello, world!");
}
使用cargo build命令可以对工程进行编译。同样的,先用help看一下:
cargo.exe-build
Compile a local package and all of its dependencies
USAGE:
cargo.exe build [OPTIONS]
OPTIONS:
-q, --quiet No output printed to stdout
-p, --package <SPEC>... Package to build (see `cargo help pkgid`)
--all Alias for --workspace (deprecated)
--workspace Build all packages in the workspace
--exclude <SPEC>... Exclude packages from the build
-j, --jobs <N> Number of parallel jobs, defaults to # of CPUs
--lib Build only this package's library
--bin <NAME>... Build only the specified binary
--bins Build all binaries
--example <NAME>... Build only the specified example
--examples Build all examples
--test <NAME>... Build only the specified test target
--tests Build all tests
--bench <NAME>... Build only the specified bench target
--benches Build all benches
--all-targets Build all targets
--release Build artifacts in release mode, with optimizations
--profile <PROFILE-NAME> Build artifacts with the specified profile
--features <FEATURES>... Space-separated list of features to activate
--all-features Activate all available features
--no-default-features Do not activate the `default` feature
--target <TRIPLE> Build for the target triple
--target-dir <DIRECTORY> Directory for all generated artifacts
--out-dir <PATH> Copy final artifacts to this directory (unstable)
--manifest-path <PATH> Path to Cargo.toml
--message-format <FMT>... Error format
--build-plan Output the build plan in JSON (unstable)
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
All packages in the workspace are built if the `--workspace` flag is supplied. The
`--workspace` flag is automatically assumed for a virtual manifest.
Note that `--exclude` has to be specified in conjunction with the `--workspace` flag.
Compilation can be configured via the use of profiles which are configured in
the manifest. The default profile for this command is `dev`, but passing
the --release flag will use the `release` profile instead.
编译的功能非常完善,支持多CPU编译,多目标编译等等。当然,我们的这个实例非常简单,不需要这些高级的功能:
cd guessing
cargo build
Compiling guessing v0.1.0 (D:\test\rust\guessing)
Finished dev [unoptimized + debuginfo] target(s) in 1.18s
默认的,cargo编译使用debug的方式,不进行优化,且包含调试信息。可以指定以release的方式编译,进行优化并去除调试信息。
cargo build --release
Compiling guessing v0.1.0 (D:\test\rust\guessing)
Finished release [optimized] target(s) in 2.26s
编译完成后,生成的exe文件在target目录中,根据编译方式的不同,保存在debug或release目录中。
Rust编译会将运行时需要的所有信息全部打包到exe中,可以完全脱离Rust环境运行,但exe文件也比较大。
当然可以找到编译出来的exe运行,但有更简单的方法,直接使用cargo run命令运行。先看帮助:
cargo.exe-run
Run a binary or example of the local package
USAGE:
cargo.exe run [OPTIONS] [--] [args]...
OPTIONS:
-q, --quiet No output printed to stdout
--bin <NAME>... Name of the bin target to run
--example <NAME>... Name of the example target to run
-p, --package <SPEC> Package with the target to run
-j, --jobs <N> Number of parallel jobs, defaults to # of CPUs
--release Build artifacts in release mode, with optimizations
--profile <PROFILE-NAME> Build artifacts with the specified profile
--features <FEATURES>... Space-separated list of features to activate
--all-features Activate all available features
--no-default-features Do not activate the `default` feature
--target <TRIPLE> Build for the target triple
--target-dir <DIRECTORY> Directory for all generated artifacts
--manifest-path <PATH> Path to Cargo.toml
--message-format <FMT>... Error format
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
ARGS:
<args>...
If neither `--bin` nor `--example` are given, then if the package only has one
bin target it will be run. Otherwise `--bin` specifies the bin target to run,
and `--example` specifies the example target to run. At most one of `--bin` or
`--example` can be provided.
All the arguments following the two dashes (`--`) are passed to the binary to
run. If you're passing arguments to both Cargo and the binary, the ones after
`--` go to the binary, the ones before go to Cargo.
在执行cargo run时,cargo会先检查目录是否存在,若不存在,则先进行编译,然后再运行。所以run的选项和build比较像。
为了演示先编译后运行的效果,先用cargo clean命令清除编译后的文件,再执行cargo run
cargo clean
cargo run
Compiling guessing v0.1.0 (D:\test\rust\guessing)
Finished dev [unoptimized + debuginfo] target(s) in 0.82s
Running `target\debug\guessing.exe`
Hello, world!
输出了Hello, world!,执行成功。
cargo不旦可以进行工程管理,还可以像python的pip那样进行包管理。
在记录如何管理包之前,先总结一下Rust中关于包的概念。
cargo new创建的,就是一个包(package),包中含有crate,crate根据new命令的参数分为两种:
包中包含crate的规则如下:
使用cargo install命令可安装指定的crate。
cargo.exe-install
Install a Rust binary. Default location is $HOME/.cargo/bin
USAGE:
cargo.exe install [OPTIONS] [--] [crate]...
OPTIONS:
-q, --quiet No output printed to stdout
--version <VERSION> Specify a version to install
--git <URL> Git URL to install the specified crate from
--branch <BRANCH> Branch to use when installing from git
--tag <TAG> Tag to use when installing from git
--rev <SHA> Specific commit to use when installing from git
--path <PATH> Filesystem path to local crate to install
--list list all installed packages and their versions
-j, --jobs <N> Number of parallel jobs, defaults to # of CPUs
-f, --force Force overwriting existing crates or binaries
--no-track Do not save tracking information (unstable)
--features <FEATURES>... Space-separated list of features to activate
--all-features Activate all available features
--no-default-features Do not activate the `default` feature
--profile <PROFILE-NAME> Install artifacts with the specified profile
--debug Build in debug mode instead of release mode
--bin <NAME>... Install only the specified binary
--bins Install all binaries
--example <NAME>... Install only the specified example
--examples Install all examples
--target <TRIPLE> Build for the target triple
--root <DIR> Directory to install packages into
--registry <REGISTRY> Registry to use
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
ARGS:
<crate>...
This command manages Cargo's local set of installed binary crates. Only
packages which have executable [[bin]] or [[example]] targets can be
installed, and all executables are installed into the installation root's
`bin` folder. The installation root is determined, in order of precedence, by
`--root`, `$CARGO_INSTALL_ROOT`, the `install.root` configuration key, and
finally the home directory (which is either `$CARGO_HOME` if set or
`$HOME/.cargo` by default).
There are multiple sources from which a crate can be installed. The default
location is crates.io but the `--git`, `--path`, and `--registry` flags can
change this source. If the source contains more than one package (such as
crates.io or a git repository with multiple crates) the `<crate>` argument is
required to indicate which crate should be installed.
Crates from crates.io can optionally specify the version they wish to install
via the `--version` flags, and similarly packages from git repositories can
optionally specify the branch, tag, or revision that should be installed. If a
crate has multiple binaries, the `--bin` argument can selectively install only
one of them, and if you'd rather install examples the `--example` argument can
be used as well.
By default cargo will refuse to overwrite existing binaries. The `--force` flag
enables overwriting existing binaries. Thus you can reinstall a crate with
`cargo install --force <crate>`.
Omitting the <crate> specification entirely will install the crate in the
current directory. This behaviour is deprecated, and it no longer works in the
Rust 2018 edition. Use the more explicit `install --path .` instead.
If the source is crates.io or `--git` then by default the crate will be built
in a temporary target directory. To avoid this, the target directory can be
specified by setting the `CARGO_TARGET_DIR` environment variable to a relative
path. In particular, this can be useful for caching build artifacts on
continuous integration systems.
只不过,cargo install命令只能安装带有可执行程序的crate,这个实例用到了rand库,用来生成随机数,这个库里面没有可执行程序。执行如下命令
cargo install rand
Updating `git://mirrors.ustc.edu.cn/crates.io-index` index
Downloaded rand v0.7.2 (registry `git://mirrors.ustc.edu.cn/crates.io-index`)
Downloaded 1 crate (111.4 KB) in 1.71s
error: specified package `rand v0.7.2` has no binaries
最后cargo报错,说rand v0.7.2没有编译好的二进制文件。我们可以使用另外的方式进行包管理。
修改Cargo.toml文件,在[dependencies]中添加如下内容:
rand = "0.7.2"
使用cargo进行编译:
cargo build
Compiling getrandom v0.1.14
Compiling cfg-if v0.1.10
Compiling ppv-lite86 v0.2.6
Compiling rand_core v0.5.1
Compiling c2-chacha v0.2.3
Compiling rand_chacha v0.2.1
Compiling rand v0.7.2
Compiling guessing v0.1.0 (D:\test\rust\guessing)
Finished dev [unoptimized + debuginfo] target(s) in 7.36s
cargo帮我们下载了rand v0.7.2以及它所依赖的其他包。当然,我们在cargo install时,虽然执行失败了,但我知道了它最新的版本号是0.7.2,如果不知道该版本,可以使用
cargo search rand
rand = "0.7.2" # Random number generators and other randomness functionality.
random_derive = "0.0.0" # Procedurally defined macro for automatically deriving rand::Rand for structs and enums
ndarray-rand = "0.11.0" # Constructors for randomized arrays. `rand` integration for `ndarray`.
fake-rand-test = "0.0.0" # Random number generators and other randomness functionality.
rand_derive = "0.5.0" # `#[derive(Rand)]` macro (deprecated).
rand_core = "0.5.1" # Core random number generator traits and tools for implementation.
pcg_rand = "0.11.1" # An implementation of the PCG family of random number generators in pure Rust
derive_rand = "0.0.0" # `#[derive]`-like functionality for the `rand::Rand` trait.
rand_macros = "0.1.10" # `#[derive]`-like functionality for the `rand::Rand` trait.
rand_seeder = "0.2.0" # A universal random number seeder based on SipHash.
... and 278 crates more (use --limit N to see more)
命令查询,也可以不填写版本号:
[dependencies]
rand = ""
这样cargo会自动安装最新版本。不过,不推荐使用这种方式,因为某些库的升级可能会对已经完成的代码造成影响。
配置完了依赖,就可以在代码中使用了,先上代码:
use rand::Rng;
fn main()
{
let number = rand::thread_rng().gen_range(1, 999);
println!("number is {}", number);
}
第一行,use类似于C/C++的include,rand是crate名,Rng是trait名。这一行的作用是引用了rand的Rng。
第五行调用rand产生一个1~999的随机数,将该数绑定到number变量。
要在Rust中使用标准输入输出,要引入标准库中的一个crate:io。因为是标准库,所以不需要指定依赖,直接使用use引入就可以了。
use std::io;
fn main()
{
let mut guess = String::new();
println!("input your guess:");
io::stdin().read_line(&mut guess);
println!("you guess:{}", guess);
}
要接收标准输入,首先要创建一个字符串用于接收输入的内容,该字符串应该是可变的。
有了接收的字符串,就可以使用io::stdin().read_line()来读取标准输入,以可变的引用的方式将读取到的内容保存到接收字符串中。
上面的代码已经可以运行,但编译时会有一个警告:
warning: unused `std::result::Result` that must be used
--> src\main.rs:17:5
|
17 | io::stdin().read_line(&mut guess);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
这个警告涉及到了Rust的错误处理机制。这里先粗略记录一下,真正学到错误处理时再详细记录。
Rust代码运行时,会发生两种错误:
这个警报的意思是说这段代码可能会发生Result类型的错误,需要进行错误处理。在这个例子中,错误处理非常简单,只需要在io::stdin().read_line(&mut guess)后增加一句.expect(“Failed to read line”),意思是当发生读取错误时,打印 “Failed to read line” 。修改完后的代码为:
use std::io;
fn main()
{
let mut guess = String::new();
println!("input your guess:");
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
println!("you guess:{}", guess);
}
这样就不会有那个警告了。
标准输出已经不陌生了,在前面的例子中,使用了很多println!宏进行输出。
println!宏的代码如下:
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(print_internals, format_args_nl)]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ({
$crate::io::_print($crate::format_args_nl!($($arg)*));
})
}
现在的我还看不太懂这段代码,总之,println!宏使用了io::_print实现的标准输出。
我们还可以使用如下的方式进行输出:
use std::io::Write;
fn main()
{
std::io::stdout().write("hello".as_bytes())
.expect("Failed to read line");
}
当然,这远没有println!宏好用。
有了前面的知识,我们就可以写出完整的猜猜看游戏了。我没有看教程的代码,自己按自己的想法写了一个,代码如下:
use std::io;
use rand::Rng;
fn main()
{
let number = rand::thread_rng().gen_range(1, 999);
loop
{
println!("猜猜看这个三位数是多少呢:");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("读取输入失败。");
println!("您猜的是{}", guess);
guess = guess.trim().to_string();
// 输入合法性校验
if !is_interger(&guess)
{
println!("请输入数字");
continue;
}
let gn = guess.parse::<i32>().unwrap(); // 字符串转为i32,read_line读取的带有回车,会造成类型转换失败,需要先进行trim
if gn == number
{
println!("恭喜你猜对了!");
break;
}
else if gn < number
{
println!("再大点儿。");
}
else
{
println!("再小点儿。");
}
}
}
fn is_interger(s:&String) -> bool
{
for c in s.chars()
{
println!("{}, {}", c, c <= '0' || c >= '9');
if c <= '0' || c >= '9'
{
return false;
}
}
return true;
}
这一部分的内容对于我确实早了一点,写的过程中发现了与move语义及借用和引用的问题,查阅了后面所有权的内容以后才得以解决。好在第一个实例还是写出来了,但与教程中的代码相比,我们代码实在丑陋不堪。python有pythonic,Rust是不是也有rustic一说呢?
实例中的例子对我有很大的启发,整段代码一个if都没有,使用了两次模式匹配解决问题:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main()
{
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
loop
{
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse()
{
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number)
{
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal =>
{
println!("You win!");
break;
}
}
}
}
第一处使用模式匹配是在处理字符串到数字的转换时。parse返回一个Result类型,而Result是一个拥有Ok或Err成员的枚举。使用match对Result进行模式匹配是从遇到错误就崩溃转换到真正处理错误的惯用方法。
第二处使用模式匹配是在比较大小时。cmp返回一个Ordering类型,而Ordering类型是一个拥有Less、Greater、Equal三个成员的枚举,这也与模式匹配的思想契合。
要适应Rust的思想,真的需要好好修炼才行。