注释种类
注释分为代码注释、文档注释、包和模块注释。代码注释方式有行注释//,块注释/* */两种。
文档注释需要位于 lib
类型的包中,例如 src/lib.rs
中。可以使用 markdown
语法。被注释的对象需要使用 pub
对外可见。Rust 提供了cargo doc --open
命令,可以用于把这些文档注释转换成 HTML
网页文件。文档行注释:/// ,文档块注释:/** */
对包和模块的注释要添加到包、模块的最上方,包级别的注释也分为两种:行注释 //!
和块注释 /*! ... */
。Rust 允许我们在文档注释中写单元测试用例!注释不仅仅是文档,还可以作为单元测试的用例运行,使用 cargo test
运行测试
文档测试中的用例还可以造成 panic
:
#![allow(unused)]
fn main() {
/// # Panics
///
/// The function panics if the second argument is zero.
///
/// ```rust
/// // panics on division by zero
/// world_hello::compute::div(10, 0);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Divide-by-zero error");
}
a / b
}
}
以上测试运行后会 panic
,
如果想要通过这种测试,可以添加 should_panic
:
/// # Panics
///
/// The function panics if the second argument is zero.
///
/// ```rust,should_panic
/// // panics on division by zero
/// world_hello::compute::div(10, 0);
/// ```
我们使用 #
将不想让用户看到的内容隐藏起来,但是又不影响测试用例的运行,
/// ```
/// # // 使用#开头的行会在文档中被隐藏起来,但是依然会在文档测试中运行
/// # fn try_main() -> Result<(), String> {
/// let res = world_hello::compute::try_div(10, 0)?;
/// # Ok(()) // returning from try_main
/// # }
/// # fn main() {
/// # try_main().unwrap();
/// #
/// # }
/// ```
pub fn try_div(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Divide-by-zero"))
} else {
Ok(a / b)
}
}
最终用户将只能看到那行没有隐藏的 let res = world_hello::compute::try_div(10, 0)?;:
一个带包文档的综合例子:
首先,使用 cargo new art
创建一个 Package art
:该命令创建了一个二进制 Package
,它
包含一个同名的二进制包:包名为 art
,包根为 src/main.rs
,
在 src
目录下再创建一个 lib.rs
文件,等同于又创建了一个库类型的包,包名也是 art
,包根为 src/lib.rs
。
将以下内容添加到 src/lib.rs
中:
//! # Art
//!
//! 未来的艺术建模库,现在的调色库
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
//! 定义颜色的类型
/// 主色
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// 副色
#[derive(Debug,PartialEq)]
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
//! 实用工具,目前只实现了调色板
use crate::kinds::*;
/// 将两种主色调成副色
/// ```rust
/// use art::utils::mix;
/// use art::kinds::{PrimaryColor,SecondaryColor};
/// assert!(matches!(mix(PrimaryColor::Yellow, PrimaryColor::Blue), SecondaryColor::Green));
/// ```
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
SecondaryColor::Green
}
}
接着,将下面内容添加到 src/main.rs
中:
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let blue = PrimaryColor::Blue;
let yellow = PrimaryColor::Yellow;
println!("{:?}",mix(blue, yellow));
}
使用 cargo run
运行,输出Green。执行cargo doc --open
命令,可以在生成文档如下:
格式化输出
Rust 中用来格式化输出的三大金刚,用途如下:
print!
将格式化文本输出到标准输出,不带换行符println!
同上,但是在行的末尾添加换行符format!
将格式化文本输出到String
字符串
还有两大护法,使用方式跟 print!
,println!
很像,但是它们输出到标准错误输出:
eprintln!("Error: Could not complete task");它们仅应该被用于输出错误信息和进度信息
Rust 统一用{}
作为格式化占位符 ,{:?}
也是占位符:
{}
适用于实现了std::fmt::Display
特征的类型,用来以更优雅、更友好的方式格式化文本,例如展示给用户{:?}
适用于实现了std::fmt::Debug
特征的类型,用于调试场景
大多数 Rust 类型都实现了 Debug
特征或者支持派生该特征,对于数值、字符串、数组,可以直接使用 {:?}
进行输出,但是对于结构体,需要派生Debug特征后,才能进行输出。而实现了 Display
特征的 Rust 类型并没有那么多,往往需要我们自定义想要的格式化方式,对于没有实现 Display
特征的,处理方式一般有三种:
- 使用
{:?}
或{:#?}
- 为自定义类型实现
Display
特征 - 使用
newtype
为外部类型实现Display
特征
其中,{:#?}
与 {:?}
几乎一样,唯一的区别在于它能更优美地输出内容。如果类型是定义在当前作用域中的,那么可以为其实现 Display
特征 :
struct Person {
name: String,
age: u8,
}
use std::fmt;
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"大佬在上,请受我一拜,小弟姓名{},年芳{},家里无田又无车,生活苦哈哈",
self.name, self.age
)
}
}
fn main() {
let p = Person {
name: "sunface".to_string(),
age: 18,
};
println!("{}", p);
}
输出如下:
大佬在上,请受我一拜,小弟姓名sunface,年芳18,家里无田又无车,生活苦哈哈
在 Rust 中,无法直接为外部类型实现外部特征,但可以使用newtype解决此问题:
struct Array(Vec<i32>);
use std::fmt;
impl fmt::Display for Array {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "数组是:{:?}", self.0)
}
}
fn main() {
let arr = Array(vec![1, 2, 3]);
println!("{}", arr);
}
输出结果
数组是:[1, 2, 3]
Array
就是我们的 newtype
,它将想要格式化输出的 Vec
包裹在内,最后只要为 Array
实现 Display
特征,即可进行格式化输出
格式化输出中的一些参数
除了默认按照依次顺序使用值去替换占位符之外(依次填充占位符),还能让指定位置的参数去替换某个占位符,例如 {1}
,表示用第二个参数替换该占位符(索引从 0 开始) 。我们还可以为参数指定名称,让对应名称的值填充占位符,但带名称的参数必须放在不带名称参数的后面,否则会报错
println!("{abc} {1}", abc = "def", 2); //编译报错,应该写成println!("{abc} {0}", 2,abc = "def");
格式化输出还可以指定输出小数点后几位, 字符串格式化默认使用空格进行填充,并且进行左对齐。数字格式化默认也是使用空格进行填充,但是右对齐。可以使用 #
号来控制数字的进制输出:
fn main() {
//----------------字符串填充-------------------
// 以下全部输出 "Hello x !"
// 为"x"后面填充空格,补齐宽度5
println!("Hello {:5}!", "x");
// 使用参数5来指定宽度
println!("Hello {:1$}!", "x", 5);
// 使用x作为占位符输出内容,同时使用5作为宽度
println!("Hello {1:0$}!", 5, "x");
// 使用有名称的参数作为宽度
println!("Hello {:width$}!", "x", width = 5);
//-----------------------------------
// 使用参数5为参数x指定宽度,同时在结尾输出参数5 => Hello x !5
println!("Hello {:1$}!{}", "x", 5);
//----------------数字填充-------------------
// 宽度是5 => Hello 5!
println!("Hello {:5}!", 5);
// 显式的输出正号 => Hello +5!
println!("Hello {:+}!", 5);
// 宽度5,使用0进行填充 => Hello 00005!
println!("Hello {:05}!", 5);
// 负号也要占用一位宽度 => Hello -0005!
println!("Hello {:05}!", -5);
//----------------对齐-------------------
// 以下全部都会补齐5个字符的长度
// 左对齐 => Hello x !
println!("Hello {:<5}!", "x");
// 右对齐 => Hello x!
println!("Hello {:>5}!", "x");
// 居中对齐 => Hello x !
println!("Hello {:^5}!", "x");
// 对齐并使用指定符号填充 => Hello x&&&&!
// 指定符号填充的前提条件是必须有对齐字符
println!("Hello {:&<5}!", "x");
//----------------精度-------------------
let v = 3.1415926;
// 保留小数点后两位 => 3.14
println!("{:.2}", v);
// 带符号保留小数点后两位 => +3.14
println!("{:+.2}", v);
// 不带小数 => 3
println!("{:.0}", v);
// 通过参数来设定精度 => 3.1416,相当于{:.4}
println!("{:.1$}", v, 4);
let s = "hi我是Sunface孙飞";
// 保留字符串前三个字符 => hi我
println!("{:.3}", s);
// {:.*}接收两个参数,第一个是精度,第二个是被格式化的值 => Hello abc!
println!("Hello {:.*}!", 3, "abcdefg");
//----------------进制-------------------
// 二进制 => 0b11011!
println!("{:#b}!", 27);
// 八进制 => 0o33!
println!("{:#o}!", 27);
// 十进制 => 27!
println!("{}!", 27);
// 小写十六进制 => 0x1b!
println!("{:#x}!", 27);
// 大写十六进制 => 0x1B!
println!("{:#X}!", 27);
// 不带前缀的十六进制 => 1b!
println!("{:x}!", 27);
// 使用0填充二进制,宽度为10 => 0b00011011!
println!("{:#010b}!", 27);
}
指数及指针地址的打印,有时确实要输出{}时,就需要使用转义:
fn main() {
println!("{:2e}", 1000000000); // => 1e9
println!("{:2E}", 1000000000); // => 1E9
let v= vec![1, 2, 3];
println!("{:p}", v.as_ptr()) // => 0x600002324050
// "{{" 转义为 '{' "}}" 转义为 '}' "\"" 转义为 '"'
// => Hello "{World}"
println!(" Hello \"{{World}}\" ");
// 不可使用 '\' 来转义 "{}"
// println!(" \{ Hello \} ")
}
println!("Hello"); // => "Hello"
println!("Hello, {}!", "world"); // => "Hello, world!"
println!("The number is {}", 1); // => "The number is 1"
println!("{:?}", (3, 4)); // => "(3, 4)"
println!("{value}", value=4); // => "4"
println!("{} {}", 1, 2); // => "1 2"
println!("{:04}", 42); // => "0042" with leading zeros