Macro std:😗
assert
判断传入的表达式是否为true
- 若为
true,则无事发生 - 若不为
true,则会直接panic
macro_rules! assert {
($cond:expr $(,)?) => { ... };
($cond:expr, $($arg:tt)+) => { ... };
}两种匹配模式
- 第一种,匹配一个表达式
- 第二种,匹配一个表达式和一个或多个 token树
示例
// 这些断言的 panic 消息是给定表达式的字符串化值。
assert!(true);
fn some_computation() -> bool { true } // 一个非常简单的函数
assert!(some_computation());
// 使用自定义消息进行断言
let x = true;
assert!(x, "x wasn't true!");
let a = 3; let b = 27;
assert!(a + b == 30, "a = {}, b = {}", a, b);assert_eq
断言两个表达式彼此相等 (使用 PartialEq)。
在 panic 上,此宏将打印表达式的值及其调试表示。
像assert!一样,此宏具有第二种形式,可以在其中提供自定义 panic 消息。
macro_rules! assert_eq {
($left:expr, $right:expr $(,)?) => { ... };
($left:expr, $right:expr, $($arg:tt)+) => { ... };
}示例
let a = 3;
let b = 1 + 2;
assert_eq!(a, b);
assert_eq!(a, b, "we are testing addition with {} and {}", a, b);assert_ne
断言两个表达式彼此不相等 (使用 PartialEq)。
在 panic 上,此宏将打印表达式的值及其调试表示。
像 assert! 一样,此宏具有第二种形式,可以在其中提供自定义 panic 消息。
macro_rules! assert_ne {
($left:expr, $right:expr $(,)?) => { ... };
($left:expr, $right:expr, $($arg:tt)+) => { ... };
}示例
let a = 3;
let b = 2;
assert_ne!(a, b);cfg
在编译时评估配置标志的布尔组合。
除了 #[cfg] 属性,还提供了此宏,以允许对配置标志进行布尔表达式评估。 这通常会减少重复的代码。
该宏的语法与 cfg属性的语法相同。
cfg! 与 #[cfg] 不同,它不会删除任何代码,只会评估为 true 或 false。 例如,当将 cfg! 用作条件时,无论 cfg! 正在评估什么,if/else 表达式中的所有块都必须有效。
macro_rules! cfg {
($($cfg:tt)*) => { ... };
}示例
let my_directory = if cfg!(windows) {
"windows-specific-directory"
} else {
"unix-directory"
};column
扩展到调用它的列号。
对于 line! 和 file!,这些宏为开发人员提供了有关源中位置的调试信息。
扩展表达式的类型为 u32,并且基于 1,因此每行的第一列的值为 1,第二列的值为 2,依此类推。 这与常见编译器或常用编辑器的错误消息一致。 返回的列是 not 必然column! 调用本身的行,而是导致 column! 宏调用的第一个宏调用。
macro_rules! column {
() => { ... };
}示例
let current_col = column!();
println!("defined on column: {current_col}");column! 计算 Unicode 代码点,而不是字节或字素。作为结果,前两次调用返回相同的值,但第三次调用没有。
let a = ("foobar", column!()).1;
let b = ("人之初性本善", column!()).1;
let c = ("f̅o̅o̅b̅a̅r̅", column!()).1; // 使用组合上划线 (U+0305)
assert_eq!(a, b);
assert_ne!(b, c);compile_error
导致编译失败,并遇到给定的错误消息。
当 crate 使用条件编译策略为错误条件提供更好的错误消息时,应使用此宏。
它是 panic! 的编译器级别的形式,但在 编译 而不是 运行时 会发出错误。
macro_rules! compile_error {
($msg:expr $(,)?) => { ... };
}宏和 #[cfg] 环境就是两个这样的示例。
如果宏传递了无效值,则发出更好的编译器错误。 没有最终分支,编译器仍然会发出错误,但是错误消息不会提及两个有效值。
macro_rules! give_me_foo_or_bar {
(foo) => {};
(bar) => {};
($x:ident) => {
compile_error!("This macro only accepts `foo` or `bar`");
}
}
give_me_foo_or_bar!(neither);
// ^ 将在编译时失败,并显示消息 "This macro only accepts `foo` or `bar`"如果许多特性之一不可用,则发出编译器错误。
#[cfg(not(any(feature = "foo", feature = "bar")))]
compile_error!("Either feature \"foo\" or \"bar\" must be enabled for this crate.");concat
将字面量串联成一个静态字符串切片。
该宏采用任意数量的逗号分隔的字面量,产生 &'static str 类型的表达式,该表达式表示所有从左到右串联的字面量。
将整数和浮点字面量进行字符串化以将其串联在一起。
macro_rules! concat {
($($e:expr),* $(,)?) => { ... };
}示例
let s = concat!("test", 10, 'b', true);
assert_eq!(s, "test10btrue");dbg
打印并返回给定表达式的值,以进行快速的调试。
macro_rules! dbg {
() => { ... };
($val:expr $(,)?) => { ... };
($($val:expr),+ $(,)?) => { ... };
}示例
let a = 2;
let b = dbg!(a * 2) + 1;
// ^-- 打印: [src/main.rs:2] a * 2 = 4
assert_eq!(b, 5);宏通过使用给定表达式的类型的 Debug 实现将值与宏调用的源位置以及表达式的源代码一起打印到 标准错误 来工作。
调用表达式上的宏会移动并获取它的所有权,然后再返回不变的求值表达式。 如果表达式的类型未实现 Copy,并且您不想放弃所有权,则可以改用 dbg!(&expr) 借用某些表达式 expr。
dbg! 宏在发行版中的工作原理完全相同。 当仅在发行版本中发生的调试问题或在发行模式下进行的调试明显更快时,此功能很有用。
请注意,宏的目的是作为调试工具,因此您应该避免在版本控制中长时间使用它 (测试和类似的情况除外)。 使用其他工具 (例如 log crate 的 debug! 宏) 可以更好地完成生产代码的调试输出。
Stability
不应依赖此宏打印的确切输出,并且可能会受到 future 的更改。
panics
如果写入 io::stderr 失败,就会出现 panics。
debug_assert
判断传入的表达式是否为true,若不为true,则panic
macro_rules! debug_assert {
($($arg:tt)*) => { ... };
}示例
// 这些断言的 panic 消息是给定表达式的字符串化值。
debug_assert!(true);
fn some_expensive_computation() -> bool { true } // 一个非常简单的函数
debug_assert!(some_expensive_computation());
// 使用自定义消息进行断言
let x = true;
debug_assert!(x, "x wasn't true!");
let a = 3; let b = 27;
debug_assert!(a + b == 30, "a = {}, b = {}", a, b);TIP
与 assert! 不同,默认情况下仅在未优化的构建中启用 debug_assert! 语句。 除非将 -C debug-assertions 传递给编译器,否则优化的构建将不执行 debug_assert! 语句。 这使 debug_assert! 对于检查成本太高而无法在发行版本中进行检查,但在开发过程中可能很有用。 扩展 debug_assert! 的结果始终是类型检查的。
未检查的断言允许处于不一致状态的程序继续运行,这可能会带来意想不到的后果,但不会引入不安全性,只要这种不安全性仅在安全代码中发生即可。
但是,断言的性能成本通常无法衡量。 因此,仅在经过全面分析后才鼓励使用 debug_assert! 替换 assert!,更重要的是,仅使用安全代码!
debug_assert_eq
断言两个表达式彼此相等。
在 panic 上,此宏将打印表达式的值及其调试表示。
与 assert_eq! 不同,默认情况下仅在未优化的构建中启用 debug_assert_eq! 语句。 除非将 -C debug-assertions 传递给编译器,否则优化的构建将不执行 debug_assert_eq! 语句。 这使 debug_assert_eq! 对于检查成本太高而无法在发行版本中进行检查,但在开发过程中可能很有用。
扩展 debug_assert_eq! 的结果始终是类型检查的。
macro_rules! debug_assert_eq {
($($arg:tt)*) => { ... };
}示例
let a = 3;
let b = 1 + 2;
debug_assert_eq!(a, b);debug_assert_ne
断言两个表达式彼此不相等。
在 panic 上,此宏将打印表达式的值及其调试表示。
与 assert_ne! 不同,默认情况下仅在未优化的构建中启用 debug_assert_ne! 语句。 除非将 -C debug-assertions 传递给编译器,否则优化的构建将不执行 debug_assert_ne! 语句。 这使 debug_assert_ne! 对于检查成本太高而无法在发行版本中进行检查,但在开发过程中可能很有用。
扩展 debug_assert_ne! 的结果始终是类型检查的。
macro_rules! debug_assert_ne {
($($arg:tt)*) => { ... };
}示例
let a = 3;
let b = 2;
debug_assert_ne!(a, b);env
在编译时检查环境变量。
该宏将在编译时扩展为指定的环境变量的值,从而产生 &'static str 类型的表达式。
如果要在运行时读取值,请改用 std::env::var。
如果未定义环境变量,则将发出编译错误。 为了不产生编译错误,请改用 option_env! 宏。
macro_rules! env {
($name:expr $(,)?) => { ... };
($name:expr, $error_msg:expr $(,)?) => { ... };
}示例
let path: &'static str = env!("PATH");
println!("the $PATH variable at the time of compiling was: {path}");您可以通过将字符串作为第二个参数传递来自定义错误消息:
let doc: &'static str = env!("documentation", "what's that?!");如果未定义 documentation 环境变量,则会出现以下错误:
error: what's that?!eprint
打印到标准错误。
等效于 print! 宏,除了输出转到 io::stderr 而不是 io::stdout。
有关用法示例,请参见 print!。
仅将 eprint! 用于错误和进度消息。 改用 print! 作为程序的主要输出。
macro_rules! eprint {
($($arg:tt)*) => { ... };
}示例
eprint!("Error: Could not complete task");panic
如果写入 io::stderr 失败,就会出现 panics。
写入非阻塞 stdout 可能会导致错误,这将导致此宏 panic。
eprintln
使用换行符打印到标准错误。
等效于 println! 宏,除了输出转到 io::stderr 而不是 io::stdout。
有关用法示例,请参见 println!。
仅将 eprintln! 用于错误和进度消息。 改用 println! 作为程序的主要输出。
macro_rules! eprintln {
() => { ... };
($($arg:tt)*) => { ... };
}示例
eprintln!("Error: Could not complete task");panic
如果写入 io::stderr 失败,就会出现 panics。
写入非阻塞 stdout 可能会导致错误,这将导致此宏 panic。
file
扩展为调用该文件的文件名。
对于 line! 和 column!,这些宏为开发人员提供了有关源中位置的调试信息。
扩展表达式的类型为 &'static str,返回的文件不是 file! 宏本身的调用,而是导致 file! 宏调用的第一个宏调用。
macro_rules! file {
() => { ... };
}示例
let this_file = file!();
println!("defined in file: {this_file}");format
使用运行时表达式的插值创建 String。
format! 收到的第一个参数是格式字符串。这必须是字符串字面量。格式字符串的作用是包含在 {} 中。
除非使用命名或位置参数,否则传递给 format! 的其他参数将以给定的顺序替换格式字符串中的 {}。有关更多信息,请参见 std::fmt。
format! 的常见用法是字符串的连接和内插。 print! 和 write! 宏使用相同的约定,具体取决于字符串的预期目标。
要将单个值转换为字符串,请使用 to_string 方法。这将使用 Display 格式 trait。
macro_rules! format {
($($arg:tt)*) => { ... };
}示例
format!("test");
format!("hello {}", "world!");
format!("x = {}, y = {y}", 10, y = 30);
let (x, y) = (1, 2);
format!("{x} + {y} = 3");TIP
如果格式化 trait 实现返回了错误,则会出现 format! panics。 这表明实现不正确,因为 fmt::Write for String 本身从不返回错误。
format_args
构造其他字符串格式宏的参数。
macro_rules! format_args {
($fmt:expr) => { ... };
($fmt:expr, $($args:tt)*) => { ... };
}此宏函数通过为每个传递的其他参数采用包含 {} 的格式字符串字面量来实现。 format_args! 准备附加参数以确保输出可以解释为字符串,并将参数规范化为单一类型。 可以将实现 Display trait 的任何值传递给 format_args!,也可以将任何 Debug 实现的形式传递给格式化字符串中的 {:?}。
该宏产生 fmt::Arguments 类型的值。可以将该值传递到 std::fmt 中的宏,以执行有用的重定向。 所有其他格式化宏 format!,write!,println! 等 都通过此代理。 format_args! 与其派生的宏不同,它避免了堆分配。
您可以使用 format_args! 在 Debug 和 Display 上下文中返回的 fmt::Arguments 值,如下所示。 该示例还显示 Debug 和 Display 的格式相同: format_args! 中的插值格式字符串。
let debug = format!("{:?}", format_args!("{} foo {:?}", 1, 2));
let display = format!("{}", format_args!("{} foo {:?}", 1, 2));
assert_eq!("1 foo 2", display);
assert_eq!(display, debug);示例
use std::fmt;
let s = fmt::format(format_args!("hello {}", "world"));
assert_eq!(s, format!("hello {}", "world"));include
根据上下文将文件解析为表达式或项。
警告: 对于多文件 Rust 项目,include! 宏可能不是您要找的。通常,多文件 Rust 项目使用 modules。
包含的文件放在周围的代码 unhygienically 中。 如果包含的文件被解析为表达式,并且变量或函数在两个文件中共享名称,则可能导致变量或函数与包含的文件预期的不同。
包含的文件相对于当前文件定位 (类似于找到模块的方式)。 提供的路径在编译时以特定于平台的方式进行解释。 因此,例如,使用 Windows 路径包含反斜杠 \ 的调用将无法在 Unix 上正确编译。
macro_rules! include {
($file:expr $(,)?) => { ... };
}TIP
include! 宏主要用于两个目的。 它用于包含在单独文件中编写的文档,并且用于包含 build artifacts usually as a result from the build.rs script。
当使用 include 宏来包含大量文档时,请记住包含的文件仍然需要是有效的 rust 语法。 也可以将 include_str 宏用作 #![doc = include_str!("...")] (在模块级别) 或 #[doc = include_str!("...")] (在项目级别) 以包含来自纯文本或 markdown 文件的文档。
假设在同一目录中有两个文件,其内容如下:
['🙈', '🙊', '🙉']
.iter()
.cycle()
.take(6)
.collect::<String>()fn main() {
let my_string = include!("monkeys.in");
assert_eq!("🙈🙊🙉🙈🙊🙉", my_string);
println!("{my_string}");
}:::
include_bytes
包含一个文件作为对字节数组的引用。
该文件相对于当前文件位于 (类似于查找模块的方式)。 提供的路径在编译时以特定于平台的方式进行解释。 因此,例如,使用 Windows 路径包含反斜杠 \ 的调用将无法在 Unix 上正确编译。
该宏将产生 &'static [u8; N] 类型的表达式,该表达式是文件的内容。
macro_rules! include_bytes {
($file:expr $(,)?) => { ... };
}示例
adiósfn main() {
let bytes = include_bytes!("spanish.in");
assert_eq!(bytes, b"adi\xc3\xb3s\n");
print!("{}", String::from_utf8_lossy(bytes)); // adiós
}include_str
包含 UTF-8 编码的文件作为字符串。
该文件相对于当前文件位于 (类似于查找模块的方式)。 提供的路径在编译时以特定于平台的方式进行解释。 因此,例如,使用 Windows 路径包含反斜杠 \ 的调用将无法在 Unix 上正确编译。
该宏将产生 &'static str 类型的表达式,该表达式是文件的内容。
macro_rules! include_str {
($file:expr $(,)?) => { ... };
}示例
adiósfn main() {
let my_str = include_str!("spanish.in");
assert_eq!(my_str, "adiós\n");
print!("{my_str}"); // adiós
}is_x86_feature_detected
x86 or x86-64
一个在运行时测试 x86/x86-64 平台上是否具有 CPU 特性的宏。
标准库中提供了此宏,它将在运行时检测是否检测到指定的 CPU 特性。
除非在整个 crate 中启用了指定特性,否则在编译时不会解析。 当前,运行时检测主要依赖于 cpuid 指令。
该宏仅使用一个参数,该参数是要测试的特性的字符串字面量。 支持的特性名称是 their 文档 中 Intel 定义的特性的小写版本。
macro_rules! is_x86_feature_detected {
("aes") => { ... };
("pclmulqdq") => { ... };
("rdrand") => { ... };
("rdseed") => { ... };
("tsc") => { ... };
("mmx") => { ... };
("sse") => { ... };
("sse2") => { ... };
("sse3") => { ... };
("ssse3") => { ... };
("sse4.1") => { ... };
("sse4.2") => { ... };
("sse4a") => { ... };
("sha") => { ... };
("avx") => { ... };
("avx2") => { ... };
("avx512f") => { ... };
("avx512cd") => { ... };
("avx512er") => { ... };
("avx512pf") => { ... };
("avx512bw") => { ... };
("avx512dq") => { ... };
("avx512vl") => { ... };
("avx512ifma") => { ... };
("avx512vbmi") => { ... };
("avx512vpopcntdq") => { ... };
("avx512vbmi2") => { ... };
("gfni") => { ... };
("vaes") => { ... };
("vpclmulqdq") => { ... };
("avx512vnni") => { ... };
("avx512bitalg") => { ... };
("avx512bf16") => { ... };
("avx512vp2intersect") => { ... };
("f16c") => { ... };
("fma") => { ... };
("bmi1") => { ... };
("bmi2") => { ... };
("lzcnt") => { ... };
("tbm") => { ... };
("popcnt") => { ... };
("fxsr") => { ... };
("xsave") => { ... };
("xsaveopt") => { ... };
("xsaves") => { ... };
("xsavec") => { ... };
("cmpxchg16b") => { ... };
("adx") => { ... };
("rtm") => { ... };
("movbe") => { ... };
("ermsb") => { ... };
("abm") => { ... };
("avx512gfni") => { ... };
("avx512vaes") => { ... };
("avx512vpclmulqdq") => { ... };
($t:tt,) => { ... };
($t:tt) => { ... };
}支持的参数
该宏支持 #[target_feature] 支持的相同名称。 但是,与 #[target_feature] 不同,此宏不支持用逗号分隔的名称。 相反,现在必须通过单独的宏调用来测试多个特性。
支持的参数有:
"aes""pclmulqdq""rdrand""rdseed""tsc""mmx""sse""sse2""sse3""ssse3""sse4.1""sse4.2""sse4a""sha""avx""avx2""avx512f""avx512cd""avx512er""avx512pf""avx512bw""avx512dq""avx512vl""avx512ifma""avx512vbmi""avx512vpopcntdq""avx512vbmi2""gfni""vaes""vpclmulqdq""avx512vnni""avx512bitalg""avx512bf16""avx512vp2intersect""f16c""fma""bmi1""bmi2""abm""lzcnt""tbm""popcnt""fxsr""xsave""xsaveopt""xsaves""xsavec""cmpxchg16b""adx""rtm""movbe""ermsb"
line
扩展为在其上被调用的行号。
对于 column! 和 file!,这些宏为开发人员提供了有关源中位置的调试信息。
扩展表达式的类型为 u32,基于 1,因此每个文件的第一行求值为 1,第二行求值为 2,依此类推。 这与常见编译器或常用编辑器的错误消息一致。 返回的行必定是 notline! 调用本身的行,而是导致 line! 宏调用的第一个宏调用。
macro_rules! line {
() => { ... };
}示例
let current_line = line!();
println!("defined on line: {current_line}");matches
返回给定表达式是否与任何给定模式匹配。
像在 match 表达式中一样,可以在模式后跟 if 和可以访问由模式绑定的名称的保护表达式。
macro_rules! matches {
($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => { ... };
}示例
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));module_path
扩展为代表当前模块路径的字符串。
当前模块路径可以被认为是引回到 crate root 的模块层次结构。 返回路径的第一部分是当前正在编译的 crate 的名称。
macro_rules! module_path {
() => { ... };
}示例
mod test {
pub fn foo() {
println!("{:#?}", module_path!());
}
}
fn main() {
test::foo(); // "rust_test::test" rust_test是我的项目名
}option_env
(可选) 在编译时检查环境变量。
如果在编译时存在指定的环境变量,它将扩展为 Option<&'static str> 类型的表达式,其值是环境变量的值的 Some。 如果不存在环境变量,则它将扩展为 None。 有关此类型的更多信息,请参见 Option<T>。 如果要在运行时读取值,请改用 std::env::var。
使用此宏时,无论是否存在环境变量,都不会发出编译时错误。
macro_rules! option_env {
($name:expr $(,)?) => { ... };
}示例
let key: Option<&'static str> = option_env!("SECRET_KEY");
println!("the secret key might be: {key:?}");panic
让当前线程 panics。
这允许程序立即终止并向程序的调用者提供反馈。
macro_rules! panic {
($($arg:tt)*) => { ... };
}此宏是在示例代码和测试中声明条件的理想方法。panic! 与 Option 和 Result 枚举的 unwrap 方法密切相关。当它们被设置为 None 或 Err 变体时,这两个实现都调用了 panic!。
使用 panic!() 时,你可以指定一个字符串有效载荷,它是使用 format! 语法构建的。当将 panic 注入到调用的 Rust 线程中时,将使用这个有效载荷,从而导致该线程完全变为 panic。
默认 std 钩子的行为,即在调用 panic 后直接运行的代码,是将消息,载荷,连同 panic!() 调用的 file/line/column 信息一起打印到 stderr。您可以使用 std::panic::set_hook() 覆盖 panic 钩子。 在钩子内部,可以通过 &dyn Any + Send 访问 panic,其中包含用于常规 panic!() 调用的 &str 或 String。 对于具有其他类型值的 panic,可以使用 panic_any。
另请参见宏 compile_error!,以获取编译期间的错误。
示例
panic!();
panic!("this is a terrible mistake!");
panic!("this is a {} {message}", "fancy", message = "message");
std::panic::panic_any(4); // panic with the value of 4 to be collected elsewhere何时使用 panic! 与 Result
Rust 语言提供了两个互补的系统来构建、表示、报告、传播、响应和丢弃错误。 这些职责统称为 “error handling.” panic! 和 Result 的相似之处在于它们都是各自错误处理系统的主要接口; 但是,这些接口附加到它们的错误的含义以及它们在各自的错误处理系统中履行的职责是不同的。
panic! 宏用于构建代表程序中已检测到的错误的错误。使用 panic!,您可以提供描述错误的消息,然后语言会使用该消息构造错误、报告错误并为您传播错误。
另一方面,Result 用于包装其他类型,这些类型代表某些计算的成功结果 Ok(T) 或代表该计算的预期运行时失败模式 Err(E) 的错误类型。 Result 与代表相关计算可能遇到的各种预期运行时故障模式的用户定义类型一起使用。Result 必须手动传播,通常需要 ? 操作员和 Try trait 的帮助,并且必须手动报告它们,通常需要 Error trait 的帮助。
有关错误处理的更多详细信息,请切换到 book 或 std::result 模块文档。
print
打印到标准输出。
等效于 println! 宏,只是在消息末尾不打印换行符。
macro_rules! print {
($($arg:tt)*) => { ... };
}注意,默认情况下,stdout 通常是行缓冲的,因此可能有必要使用 io::stdout().flush() 以确保立即发出输出。
print! 宏将锁定每个调用的标准输出。如果您在热循环内调用 print!,则此行为可能是循环的瓶颈。 为了避免这种情况,用 io::stdout().lock() 锁定 stdout:
use std::io::{stdout, Write};
let mut lock = stdout().lock();
write!(lock, "hello world").unwrap();示例
use std::io::{self, Write};
print!("this ");
print!("will ");
print!("be ");
print!("on ");
print!("the ");
print!("same ");
print!("line ");
io::stdout().flush().unwrap();
print!("this string has a newline, why not choose println! instead?\n");
io::stdout().flush().unwrap();panic
如果写入 io::stdout() 失败,就会出现 panics。
写入非阻塞 stdout 可能会导致错误,这将导致此宏 panic。
println
使用换行符打印到标准输出。
在所有平台上,新行是一个换行符 (也就是 \n/U+000A),并不包含回车符 (也就是 \r/U+000D)。
此宏使用与 format! 相同的语法,但改为写入标准输出。 有关详细信息,请参见 std::fmt。
macro_rules! println {
() => { ... };
($($arg:tt)*) => { ... };
}println! 宏将锁定每个调用的标准输出。 如果您在热循环内调用 println!,则此行为可能是循环的瓶颈。 为了避免这种情况,用 io::stdout().lock() 锁定 stdout:
use std::io::{stdout, Write};
let mut lock = stdout().lock();
writeln!(lock, "hello world").unwrap();Panic
如果写入io::stdout 失败,就会出现 panics。
写入非阻塞 stdout 可能会导致错误,这将导致此宏 panic。
println!(); // 只打印换行符
println!("hello there!");
println!("format {} arguments", "some");
let local_variable = "some";
println!("format {local_variable} arguments");stringify
对其参数进行字符串化。
该宏将产生 &'static str 类型的表达式,该表达式是传递给该宏的所有 tokens 的字符串化。 宏调用本身的语法没有任何限制。
请注意,输入 tokens 的扩展结果可能会在 future 中发生变化。如果您依赖输出,则应格外小心。
macro_rules! stringify {
($($t:tt)*) => { ... };
}示例
let one_plus_one = stringify!(1 + 1);
assert_eq!(one_plus_one, "1 + 1");thread_local
声明一个新的 std::thread::LocalKey 类型的线程本地存储密钥。
macro_rules! thread_local {
() => { ... };
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const { $init:expr }; $($rest:tt)*) => { ... };
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const { $init:expr }) => { ... };
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => { ... };
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => { ... };
}宏可以包装任意数量的静态声明,并使它们成为局部线程。 允许每个静态的公开和属性。Example:
use std::cell::RefCell;
thread_local! {
pub static FOO: RefCell<u32> = RefCell::new(1);
static BAR: RefCell<f32> = RefCell::new(1.0);
}
FOO.with(|foo| assert_eq!(*foo.borrow(), 1));
BAR.with(|bar| assert_eq!(*bar.borrow(), 1.0));此宏支持特殊的 const {} 语法,当初始化表达式可以被评估为常量时可以使用该语法。 这可以启用可以避免延迟初始化的更高效的线程本地实现。 对于不是 need to be dropped 的类型,这可以实现更高效的实现,无需跟踪任何其他状态。
use std::cell::Cell;
thread_local! {
pub static FOO: Cell<u32> = const { Cell::new(1) };
}
FOO.with(|foo| assert_eq!(foo.get(), 1));todo
表示未完成的代码。
如果您正在制作原型并且只想要一个占位符让您的代码通过类型分析,这将很有用。
macro_rules! todo {
() => { ... };
($($arg:tt)+) => { ... };
}panic
这将始终为 panic!。
TIP
unimplemented!和 todo! 之间的区别在于,尽管 todo! 传达了稍后实现该功能的意图,并且消息为 “not yet implemented”,但 unimplemented! 并未提出任何此类声明。 它的消息是 “not implemented”。 还有一些 IDE 会标记 todo!。
示例
这是一些正在进行的代码的示例。我们有一个 Foo trait:
trait Foo {
fn bar(&self);
fn baz(&self);
}我们想在其中一种类型上实现 Foo,但我们也想首先仅在 bar() 上工作。为了编译我们的代码,我们需要实现 baz(),因此我们可以使用 todo!:
struct MyStruct;
impl Foo for MyStruct {
fn bar(&self) {
// 实现在这里
}
fn baz(&self) {
// 让我们现在不必担心实现 baz()
todo!();
}
}
fn main() {
let s = MyStruct;
s.bar();
// 我们甚至没有使用 baz(),所以很好。
}unimplemented
通过 panic 并带有 “not implemented” 的消息来指示未实现的代码。
这允许您的代码进行类型检查,如果您正在设计原型或实现需要多个您不打算使用所有方法的特征,这将非常有用。
macro_rules! unimplemented {
() => { ... };
($($arg:tt)+) => { ... };
}unimplemented! 和 todo! 之间的区别在于,尽管 todo! 传达了稍后实现该功能的意图,并且消息为 “not yet implemented”,但 unimplemented! 并未提出任何此类声明。 它的消息是 “not implemented”。 还有一些 IDE 会标记 todo!。
panic
这将始终是 panic!,因为 unimplemented! 只是 panic! 的简写,带有固定的特定消息。
像 panic! 一样,此宏具有用于显示自定义值的第二种形式。
假设我们有一个 Foo trait:
trait Foo {
fn bar(&self) -> u8;
fn baz(&self);
fn qux(&self) -> Result<u64, ()>;
}我们想为 ‘MyStruct’ 实现 Foo,但是由于某些原因,只有实现 bar() 函数才有意义。 baz() 和 qux() 仍然需要在我们的 Foo 实现中定义,但我们可以在它们的定义中使用 unimplemented! 来允许我们的代码编译。
如果达到未实现的方法,我们仍然希望程序停止运行。
struct MyStruct;
impl Foo for MyStruct {
fn bar(&self) -> u8 {
1 + 1
}
fn baz(&self) {
// `baz` 和 `MyStruct` 没有任何意义,因此我们完全没有逻辑。
// 这将显示 "thread 'main' panicked at 'not implemented'"。
unimplemented!();
}
fn qux(&self) -> Result<u64, ()> {
// 我们这里有一些逻辑,我们可以向未实现中添加一条消息! 显示我们的遗漏。
// 这将显示: "thread 'main' panicked at 'not implemented: MyStruct isn't quxable'"。
unimplemented!("MyStruct isn't quxable");
}
}
fn main() {
let s = MyStruct;
s.bar();
}unreachable
表示无法访问的代码。
macro_rules! unreachable {
($($arg:tt)*) => { ... };
}每当编译器无法确定某些代码不可访问时,此功能就很有用。例如:
- 让分支与守卫条件匹配。
- 动态终止的循环。
- 动态终止的迭代器。
如果确定代码不可访问不正确,则程序立即以 panic! 终止。
unreachable_unchecked 函数是该宏中不安全的副本,如果到达代码,它将导致未定义的行为。
示例
match 分支:
fn foo(x: Option<i32>) {
match x {
Some(n) if n >= 0 => println!("Some(Non-negative)"),
Some(n) if n < 0 => println!("Some(Negative)"),
Some(_) => unreachable!(), // 如果注释掉,就会编译错误
None => println!("None")
}
}Iterators:
fn divide_by_three(x: u32) -> u32 { // x/3 最差的实现之一
for i in 0.. {
if 3*i < i { panic!("u32 overflow"); }
if x < 3*i { return i-1; }
}
unreachable!("The loop should always return");
}vec
创建一个包含参数的 Vec。
macro_rules! vec {
() => { ... };
($elem:expr; $n:expr) => { ... };
($($x:expr),+ $(,)?) => { ... };
}vec! 允许使用与数组表达式相同的语法定义 Vec。 该宏有两种形式:
- 创建一个包含给定元素列表的
Vec:
let v = vec![1, 2, 3];
assert_eq!(v[0], 1);
assert_eq!(v[1], 2);
assert_eq!(v[2], 3);- 根据给定的元素和大小创建
Vec:
let v = vec![1; 3];
assert_eq!(v, [1, 1, 1]);请注意,与数组表达式不同,此语法支持所有实现 Clone 的元素,并且元素的数量不必是常量。
这将使用 clone 复制表达式,因此在具有非标准 Clone 实现的类型上使用此表达式时应格外小心。 例如,vec![Rc::new(1); 5] 将对相同的 boxed 整数值创建五个引用的 vector,而不是对 boxed 整数独立引用的五个引用。
另外,请注意,允许使用 vec![expr; 0],并产生一个空的 vector。 然而,这仍然会计算 expr,并立即丢弃结果值,因此请注意副作用。
write
将格式化的数据写入缓冲区。
该宏接受 writer,格式字符串和参数列表。 参数将根据指定的格式字符串进行格式化,并将结果传递到 writer。 使用 write_fmt 方法时,writer 可以是任何值; 通常,这来自 fmt::Write 或 io::Write trait 的实现。 宏返回 write_fmt 方法返回的任何内容; 通常是 fmt::Result 或 io::Result。
macro_rules! write {
($dst:expr, $($arg:tt)*) => { ... };
}示例
use std::io::Write;
fn main() -> std::io::Result<()> {
let mut w = Vec::new();
write!(&mut w, "test")?;
write!(&mut w, "formatted {}", "arguments")?;
assert_eq!(w, b"testformatted arguments");
Ok(())
}模块可以同时在实现两者的对象上导入 std::fmt::Write 和 std::io::Write 以及调用 write!,因为对象通常不会同时实现两者。
但是,模块必须避免 trait 名称之间的冲突,例如将它们导入为 _ 或以其他方式重命名它们:
use std::fmt::Write as _;
use std::io::Write as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut s = String::new();
let mut v = Vec::new();
write!(&mut s, "{} {}", "abc", 123)?; // 使用 fmt::Write::write_fmt
write!(&mut v, "s = {:?}", s)?; // 使用 io::Write::write_fmt
assert_eq!(v, b"s = \"abc 123\"");
Ok(())
}如果您还需要 trait 名称本身,例如在您的类型上实现一个或两个,请导入包含模块,然后用前缀命名它们:
use std::fmt::{self, Write as _};
use std::io::{self, Write as _};
struct Example;
impl fmt::Write for Example {
fn write_str(&mut self, _s: &str) -> core::fmt::Result {
unimplemented!();
}
}Note: 该宏也可以在 no_std 设置中使用。 在 no_std 设置中,您负责组件的实现细节。
use core::fmt::Write;
struct Example;
impl Write for Example {
fn write_str(&mut self, _s: &str) -> core::fmt::Result {
unimplemented!();
}
}
let mut m = Example{};
write!(&mut m, "Hello World").expect("Not written");writeln
将格式化的数据写入到缓冲区,并追加一个换行符。
在所有平台上,新行是一个换行符 (也就是 \n/U+000A),并不包含回车符 (也就是 \r/U+000D)。
有关更多信息,请参见 write!。有关格式字符串语法的信息,请参见 std::fmt。
macro_rules! writeln {
($dst:expr $(,)?) => { ... };
($dst:expr, $($arg:tt)*) => { ... };
}示例
use std::io::{Write, Result};
fn main() -> Result<()> {
let mut w = Vec::new();
writeln!(&mut w)?;
writeln!(&mut w, "test")?;
writeln!(&mut w, "formatted {}", "arguments")?;
assert_eq!(&w[..], "\ntest\nformatted arguments\n".as_bytes());
Ok(())
}