fn main() { println!("hello rust");}
随着 Facebook 的 Libra 项目出炉,Rust 一会儿火了,这是 Rust 有史以来最大的项目,但随着全球数字货币的兴起,Rust 的旅程可能才刚刚开始。编程
虽然你可能还不太了解 Rust,但在开发者眼中,Rust 真香!连续 4 年,在 Stack Overflow 开发者「最受喜好编程语言」评选中,Rust 都是第一名。小程序
2015 年 5 月 15 日,Rust 正式发布了 1.0 版本。4 年来,它凭借着「安全」和「高并发」两个特性,受到了愈来愈多开发者的喜好。Rust 正以势如破竹之势占领区块链新兴项目市场,不少著名的老项目也在考虑转向使用 Rust 重写。安全
Rust 的语言特性(安全、高性能、并发编程)与区块链的特性(分布式、加密、安全敏感)天生契合,不少著名的区块链项目已经选择使用 Rust 做为其开发语言,包括:Parity、Polkadot、Substrate、Grin、Ethereum 经典、Holochain、Cardano-Rust、Exonum、Lighthouse、Nimiq、Nervos、Conflux-Rust、Codechain、Witnet 等,更不用说即将到来的 Libra。并发
相信,选择使用 Rust 做为第一开发语言的区块链项目也会愈来愈多,咱们会迎来一波的 Rust 语言学习高潮,而区块链开发者的薪资有多高,相信你们都清楚。编程语言
实验楼上线了一门【免费】的 Rust 教程 —— 《经过例子学 Rust》。课程改编自经典教材《Rust By Example》,并根据教材内容配置了线上实验环境,和挑战测试。每个知识点都有配套的实例和小练习,让你们轻松地掌握这门语言。分布式
想要学习的朋友能够点击 《经过例子学 Rust》哦。函数
接下来,你们就跟着我熟悉一下 Rust 的一些基础语法吧,用 Rust 写出你的第一个小程序。高并发
本文建议收藏,这样随时均可以拿出来巩固一下基础 Rust 知识。性能
如下是 《经过例子学 Rust》第一节内容:学习
简介
Rust 是一门注重安全(safety)、速度(speed)和并发(concurrency)的现代系统编程语言。Rust 经过内存安全来实现以上目标,但不用垃圾回收机制(garbage collection, GC)。
本课程为《经过例子学 Rust》的在线实验版本,经过在线实验一系列程序例子,一步步完成 Rust 编程语言的入门。欢迎在课程仓库中参与修订和完善,仓库地址见 经过例子学 Rust - 在线实验版。全部文档内容版权跟随中文及英文原文档的版权(版权为 MIT 协议 或 Apache 协议)。
知识点
本节实验的主要内容包括如下知识点:
- 课程介绍
- 如何编写第一个程序
- Hello World 程序详解
- 注释
- 格式化输出
Hello World
咱们的第一个程序将打印传说中的 "Hello World" 消息,下面是完整的程序代码和编译运行过程。
这是传统的 Hello World 程序的源码。首先,在实验楼 WebIDE 中 /home/project 目录下新建 hello.rs 文件,编写如下代码(以 // 开头的注释内容能够没必要输入):
// 这是注释内容,将会被编译器忽略掉 // 能够单击那边的按钮 "Run" 来测试这段代码 -> // 若想用键盘操做,可使用快捷键 "Ctrl + Enter" 来运行 // 这段代码支持编辑,你能够自由地修改代码! // 经过单击 "Reset" 按钮可使代码恢复到初始状态 -> // 这是主函数 fn main() { // 调用编译生成的可执行文件时,这里的语句将被运行。 // 将文本打印到控制台 println!("Hello World!"); }
println! 是一个 宏(macros),能够将文本输出到控制台(console)。
在实验楼 WebIDE 终端中执行如下命令,使用 Rust 的编译器 rustc 从源程序生成可执行文件:
$ cd /home/project $ rustc hello.rs
使用 rustc 编译后将获得可执行文件 hello,使用如下命令来运行生成的文件 hello:
$ ./hello
执行后的结果以下所示:
动手试一试
请尝试下在你的 hello.rs 程序中增长一行代码,再一次使用宏 println!,获得下面结果:
Hello World! I'm a Rustacean!
注释
注释对任何程序都不可缺乏,一样 Rust 支持几种不一样的注释方式。
-
普通注释,其内容将被编译器忽略掉:
-
// 单行注释,注释内容直到行尾。
-
/* 块注释, 注释内容一直到结束分隔符。*/
-
文档注释,其内容将被解析成 HTML 帮助文档:
-
/// 为接下来的项生成帮助文档。
-
//! 为注释所属于的项(译注:如 crate、模块或函数)生成帮助文档。
fn main() { // 这是行注释的例子 // 注意有两个斜线在本行的开头 // 在这里面的全部内容都不会被编译器读取 // println!("Hello, world!"); // 请运行一下,你看到结果了吗?如今请将上述语句的两条斜线删掉,并从新运行。 /* * 这是另一种注释——块注释。通常而言,行注释是推荐的注释格式, * 不过块注释在临时注释大块代码特别有用。/* 块注释能够 /* 嵌套, */ */ * 因此只需不多按键就可注释掉这些 main() 函数中的行。/*/*/* 本身试试!*/*/*/ */ /* 注意,上面的例子中纵向都有 `*`,这只是一种风格,实际上这并非必须的。 */ // 观察块注释是如何简单地对表达式进行修改的,行注释则不能这样。 // 删除注释分隔符将会改变结果。 let x = 5 + /* 90 + */ 5; println!("Is `x` 10 or 100? x = {}", x); }
格式化输出
打印操做由 std::fmt 里面所定义的一系列 宏 来处理,包括:
- format!:将格式化文本写到 字符串(String)。译注:字符串 是返回值不是参数。
- print!:与 format! 相似,但将文本输出到控制台(io::stdout)。
- println!: 与 print! 相似,但输出结果追加一个换行符。
- eprint!:与 format! 相似,但将文本输出到标准错误(io::stderr)。
- eprintln!:与 eprint! 相似,但输出结果追加一个换行符。
这些宏都以相同的作法解析(parse)文本。另外有个优势是格式化的正确性会在编译时检查。
新建 format.rs 文件,编写代码以下。
fn main() { // 一般状况下,`{}` 会被任意变量内容所替换。 // 变量内容会转化成字符串。 println!("{} days", 31); // 不加后缀的话,31 就自动成为 i32 类型。 // 你能够添加后缀来改变 31 的类型。 // 用变量替换字符串有多种写法。 // 好比可使用位置参数。 println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // 可使用命名参数。 println!("{subject} {verb} {object}", object="the lazy dog", subject="the quick brown fox", verb="jumps over"); // 能够在 `:` 后面指定特殊的格式。 println!("{} of {:b} people know binary, the other half don't", 1, 2); // 你能够按指定宽度来右对齐文本。 // 下面语句输出 " 1",5 个空格后面连着 1。 println!("{number:>width$}", number=1, width=6); // 你能够在数字左边补 0。下面语句输出 "000001"。 println!("{number:>0width$}", number=1, width=6); // println! 会检查使用到的参数数量是否正确。 println!("My name is {0}, {1} {0}", "Bond"); // 改正 ^ 补上漏掉的参数:"James" // 建立一个包含单个 `i32` 的结构体(structure)。命名为 `Structure`。 #[allow(dead_code)] struct Structure(i32); // 可是像结构体这样的自定义类型须要更复杂的方式来处理。 // 下面语句没法运行。 println!("This struct `{}` won't print...", Structure(3)); // 改正 ^ 注释掉此行。 }
std::fmt 包含多种 traits(trait 有「特征,特性」等意思)来控制文字显示,其中重要的两种 trait 的基本形式以下:
- fmt::Debug:使用 {:?} 标记。格式化文本以供调试使用。
- fmt::Display:使用 {} 标记。以更优雅和友好的风格来格式化文本。
上例使用了 fmt::Display,由于标准库提供了那些类型的实现。若要打印自定义类型的文本,须要更多的步骤。
动手试一试
- 改正上面代码中的两个错误(见代码注释中的「改正」),使它能够没有错误地运行。
- 再用一个 println! 宏,经过控制显示的小数位数来打印:Pi is roughly 3.142 (Pi 约等于 3.142)。为了达到练习目的,使用 let pi = 3.141592 做为 Pi 的近似 值。
提示:设置小数位的显示格式能够参考文档 std::fmt。
调试(Debug)
全部的类型,若想用 std::fmt 的格式化 trait 打印出来,都要求实现这个 trait。自动的实现只为一些类型提供,好比 std 库中的类型。全部其余类型都必须手动实现。
fmt::Debug 这个 trait 使这项工做变得至关简单。全部类型都能推导(derive,即自动建立)fmt::Debug 的实现。可是 fmt::Display 须要手动实现。
// 这个结构体不能使用 `fmt::Display` 或 `fmt::Debug` 来进行打印。 struct UnPrintable(i32); // `derive` 属性会自动建立所需的实现,使这个 `struct` 能使用 `fmt::Debug` 打印。 #[derive(Debug)] struct DebugPrintable(i32);
全部 std 库类型都天生可使用 {:?} 来打印。新建 format1.rs 文件,编写代码以下:
// 推导 `Structure` 的 `fmt::Debug` 实现。 // `Structure` 是一个包含单个 `i32` 的结构体。 #[derive(Debug)] struct Structure(i32); // 将 `Structure` 放到结构体 `Deep` 中。而后使 `Deep` 也可以打印。 #[derive(Debug)] struct Deep(Structure); fn main() { // 使用 `{:?}` 打印和使用 `{}` 相似。 println!("{:?} months in a year.", 12); println!("{1:?} {0:?} is the {actor:?} name.", "Slater", "Christian", actor="actor's"); // `Structure` 也能够打印! println!("Now {:?} will print!", Structure(3)); // 使用 `derive` 的一个问题是不能控制输出的形式。 // 假如我只想展现一个 `7` 怎么办? println!("Now {:?} will print!", Deep(Structure(7))); }
在实验楼 WebIDE 终端中编译并运行程序。
$ rustc format1.rs $ ./format1
执行结果以下所示:
因此 fmt::Debug 确实使这些内容能够打印,可是牺牲了一些美感。Rust 也经过 {:#?} 提供了「美化打印」的功能:
#[derive(Debug)] struct Person<'a> { name: &'a str, age: u8 } fn main() { let name = "Peter"; let age = 27; let peter = Person { name, age }; // 美化打印 println!("{:#?}", peter); }
将上面代码添加到 format1.rs 中后,编译并运行的结果以下所示:
你能够经过手动实现 fmt::Display 来控制显示效果。
显示(Display)
fmt::Debug 一般看起来不太简洁,所以自定义输出的外观常常是更可取的。这须要经过手动实现 fmt::Display 来作到。fmt::Display 采用 {} 标记。实现方式看起来像这样:
// (使用 `use`)导入 `fmt` 模块使 `fmt::Display` 可用 use std::fmt; // 定义一个结构体,我们会为它实现 `fmt::Display`。如下是个简单的元组结构体 // `Structure`,包含一个 `i32` 元素。 struct Structure(i32); // 为了使用 `{}` 标记,必须手动为类型实现 `fmt::Display` trait。 impl fmt::Display for Structure { // 这个 trait 要求 `fmt` 使用与下面的函数彻底一致的函数签名 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此 // 结果代表操做成功或失败。注意 `write!` 的用法和 `println!` 很类似。 write!(f, "{}", self.0) } }
fmt::Display 的效果可能比 fmt::Debug 简洁,但对于 std 库来讲,这就有一个问题。模棱两可的类型该如何显示呢?举个例子,假设标准库对全部的 Vec 都实现了同一种输出样式,那么它应该是哪一种样式?下面两种中的一种吗?
- Vec :/:/etc:/home/username:/bin(使用 : 分割)
- Vec :1,2,3(使用 , 分割)
咱们没有这样作,由于没有一种合适的样式适用于全部类型,标准库也并不擅自规定一种样式。对于 Vec 或其余任意泛型容器(generic container),fmt::Display 都没有实现。所以在这些泛型的状况下要用 fmt::Debug。
这并非一个问题,由于对于任何非泛型的容器类型, fmt::Display 都可以实现。新建 display.rs 文件,编写代码以下:
use std::fmt; // (使用 `use`)导入 `fmt` 模块使 `fmt::Display` 可用 // 带有两个数字的结构体。推导出 `Debug`,以便与 `Display` 的输出进行比较。 #[derive(Debug)] struct MinMax(i64, i64); // 实现 `MinMax` 的 `Display`。 impl fmt::Display for MinMax { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 使用 `self.number` 来表示各个数据。 write!(f, "({}, {})", self.0, self.1) } } // 为了比较,定义一个含有具名字段的结构体。 #[derive(Debug)] struct Point2D { x: f64, y: f64, } // 相似地对 `Point2D` 实现 `Display` impl fmt::Display for Point2D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 自定义格式,使得仅显示 `x` 和 `y` 的值。 write!(f, "x: {}, y: {}", self.x, self.y) } } fn main() { let minmax = MinMax(0, 14); println!("Compare structures:"); println!("Display: {}", minmax); println!("Debug: {:?}", minmax); let big_range = MinMax(-300, 300); let small_range = MinMax(-3, 3); println!("The big range is {big} and the small is {small}", small = small_range, big = big_range); let point = Point2D { x: 3.3, y: 7.2 }; println!("Compare points:"); println!("Display: {}", point); println!("Debug: {:?}", point); // 报错。`Debug` 和 `Display` 都被实现了,但 `{:b}` 须要 `fmt::Binary` // 获得实现。这语句不能运行。 // println!("What does Point2D look like in binary: {:b}?", point); }
程序运行的结果以下所示:
fmt::Display 被实现了,而 fmt::Binary 没有,所以 fmt::Binary 不能使用。 std::fmt 有不少这样的 trait,它们都要求有各自的实现。这些内容将在 后面的 std::fmt 章节中详细介绍。
动手试一试
检验上面例子的输出,而后在示例程序中,仿照 Point2D 结构体增长一个复数结构体。使用同样的方式打印,输出结果要求是这个样子:
Display: 3.3 + 7.2i Debug: Complex { real: 3.3, imag: 7.2 }
测试实例:List
对一个结构体实现 fmt::Display,其中的元素须要一个接一个地处理到,这可能会很麻烦。问题在于每一个 write! 都要生成一个 fmt::Result。正确的实现须要处理全部的 Result。Rust 专门为解决这个问题提供了 ? 操做符。
在 write! 上使用 ? 会像是这样:
// 对 `write!` 进行尝试(try),观察是否出错。若发生错误,返回相应的错误。 // 不然(没有出错)继续执行后面的语句。 write!(f, "{}", value)?;
另外,你也可使用 try! 宏,它和 ? 是同样的。这种写法比较罗嗦,故再也不推荐, 但在老一些的 Rust 代码中仍会看到。使用 try! 看起来像这样:
try!(write!(f, "{}", value));
有了 ?,对一个 Vec 实现 fmt::Display 就很简单了。新建 vector.rs 文件,编写代码以下:
use std::fmt; // 导入 `fmt` 模块。 // 定义一个包含单个 `Vec` 的结构体 `List`。 struct List(Vec<i32>); impl fmt::Display for List { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 使用元组的下标获取值,并建立一个 `vec` 的引用。 let vec = &self.0; write!(f, "[")?; // 使用 `v` 对 `vec` 进行迭代,并用 `count` 记录迭代次数。 for (count, v) in vec.iter().enumerate() { // 对每一个元素(第一个元素除外)加上逗号。 // 使用 `?` 或 `try!` 来返回错误。 if count != 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } // 加上配对中括号,并返回一个 fmt::Result 值。 write!(f, "]") } } fn main() { let v = List(vec![1, 2, 3]); println!("{}", v); }
程序运行的结果以下:
动手试一试:
更改程序使 vector 里面每一个元素的下标也可以打印出来。新的结果以下:
[0: 1, 1: 2, 2: 3]
格式化
咱们已经看到,格式化的方式是经过格式字符串来指定的:
- format!("{}", foo) -> "3735928559"
- format!("0x{:X}", foo) -> "0xDEADBEEF"
- format!("0o{😮}", foo) -> "0o33653337357"
根据使用的参数类型是 X、o 仍是未指定,一样的变量(foo)可以格式化成不一样的形式。
这个格式化的功能是经过 trait 实现的,每种参数类型都对应一种 trait。最多见的格式化 trait 就是 Display,它能够处理参数类型为未指定的状况,好比 {}。
新建 format2.rs 文件,编写代码以下:
use std::fmt::{self, Formatter, Display}; struct City { name: &'static str, // 纬度 lat: f32, // 经度 lon: f32, } impl Display for City { // `f` 是一个缓冲区(buffer),此方法必须将格式化后的字符串写入其中 fn fmt(&self, f: &mut Formatter) -> fmt::Result { let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' }; let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' }; // `write!` 和 `format!` 相似,但它会将格式化后的字符串写入 // 一个缓冲区(即第一个参数f)中。 write!(f, "{}: {:.3}°{} {:.3}°{}", self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c) } } #[derive(Debug)] struct Color { red: u8, green: u8, blue: u8, } fn main() { for city in [ City { name: "Dublin", lat: 53.347778, lon: -6.259722 }, City { name: "Oslo", lat: 59.95, lon: 10.75 }, City { name: "Vancouver", lat: 49.25, lon: -123.1 }, ].iter() { println!("{}", *city); } for color in [ Color { red: 128, green: 255, blue: 90 }, Color { red: 0, green: 3, blue: 254 }, Color { red: 0, green: 0, blue: 0 }, ].iter() { // 在添加了针对 fmt::Display 的实现后,请改用 {} 检验效果。 println!("{:?}", *color) } }
程序运行的结果以下:
在 fmt::fmt 文档中能够查看格式化 traits 一览表和它们的参数类型。
动手试一试
为上面的 Color 结构体实现 fmt::Display,应获得以下的输出结果:
RGB (128, 255, 90) 0x80FF5A RGB (0, 3, 254) 0x0003FE RGB (0, 0, 0) 0x000000
若是感到疑惑,可看下面两条提示:
- 你可能须要屡次列出每一个颜色,
- 你可使用 :02 补零使位数为 2 位。
实验总结
本节实验中咱们学习了如下的内容:
- 课程介绍
- 如何编写第一个程序
- Hello World 程序详解
- 注释
- 格式化输出
还想继续学习的小伙伴,点击 《经过例子学 Rust》, 免费学!