- 1:什么是属性?
- 2:条件编译属性
- 2.1:#[cfg] 和 #[cfg_attr] - 条件编译详解
- 3:代码生成属性
- 3.1:#[derive] - 自动实现 Trait 详解
- 4:测试相关属性
- 4.1:#[test] - 单元测试详解
- 5:代码警告控制
- 5.1:#[allow], #[warn], #[deny], #[forbid] - Lint 控制详解
- 6:文档属性
- 6.1:#[doc]
- 7:内联和优化属性
- 7.1:#[inline] - 函数内联详解
- 8:功能开关属性
- 8.1:#[feature]
- 9:模块和可见性属性
- 9.1:#[path]
- 10:宏相关属性
- 10.1:#[macro_use] 和 #[macro_export]
- 11:内存布局属性
- 11.1:#[repr] - 内存表示详解
- 12:弃用属性
- 12.1:#[deprecated]
- 13:标准库和外部 crate 常用属性
- 13.1:Serde(序列化)
- 13.2:异步相关
- 14:条件编译实用示例
- 15:最佳实践
- 16:参考资源
什么是属性?
属性是 Rust 中用于为代码添加元数据的机制。它们以 #[...] 或 #![...] 的形式出现,可以应用于模块、函数、结构体等各种代码项。
#[attribute]- 外部属性,应用于紧随其后的项#![attribute]- 内部属性,应用于包含它的项(通常是模块或 crate)
条件编译属性
#[cfg] 和 #[cfg_attr] - 条件编译详解
什么是条件编译?
条件编译允许你根据不同的编译配置(平台、特性、构建模式等)选择性地包含或排除代码。这些代码在不满足条件时完全不会被编译,而不是在运行时检查。
#[cfg] - 条件包含
// 基本语法
#[cfg(条件)]
fn conditional_function() {
// 仅在条件满足时编译
}
// 实际例子:平台特定代码
#[cfg(target_os = "linux")]
fn get_config_path() -> &'static str {
"/etc/myapp/config.toml"
}
#[cfg(target_os = "windows")]
fn get_config_path() -> &'static str {
"C:\\ProgramData\\MyApp\\config.toml"
}
#[cfg(target_os = "macos")]
fn get_config_path() -> &'static str {
"/Library/Application Support/MyApp/config.toml"
}
常见的配置谓词
1. 操作系统检测
#[cfg(target_os = "linux")] // Linux
#[cfg(target_os = "windows")] // Windows
#[cfg(target_os = "macos")] // macOS
#[cfg(target_os = "ios")] // iOS
#[cfg(target_os = "android")] // Android
#[cfg(target_os = "freebsd")] // FreeBSD
#[cfg(unix)] // 所有 Unix-like 系统
#[cfg(windows)] // Windows 系统
2. 架构检测
#[cfg(target_arch = "x86_64")] // 64位 x86
#[cfg(target_arch = "x86")] // 32位 x86
#[cfg(target_arch = "arm")] // ARM
#[cfg(target_arch = "aarch64")] // ARM64
#[cfg(target_arch = "wasm32")] // WebAssembly
#[cfg(target_pointer_width = "64")] // 64位指针
3. 构建模式
#[cfg(debug_assertions)] // Debug 模式(cargo build)
#[cfg(not(debug_assertions))] // Release 模式(cargo build --release)
// 实际应用
fn expensive_check(data: &[u8]) {
#[cfg(debug_assertions)]
{
// 仅在 Debug 模式下进行昂贵的验证
assert!(data.len() < 1000, "Data too large");
for byte in data {
assert!(*byte < 128, "Invalid byte");
}
}
// Release 模式下这些检查完全不存在
}
4. 测试环境
#[cfg(test)]
mod tests {
// 仅在运行测试时编译
use super::*;
#[test]
fn test_something() {
assert_eq!(2 + 2, 4);
}
}
// 测试辅助函数
#[cfg(test)]
fn create_test_data() -> Vec<u8> {
vec![1, 2, 3, 4, 5]
}
5. 特性标志(Features)
// Cargo.toml:
// [features]
// encryption = ["aes", "rsa"]
// networking = ["tokio"]
#[cfg(feature = "encryption")]
mod crypto {
pub fn encrypt(data: &[u8]) -> Vec<u8> {
// 加密实现
}
}
#[cfg(feature = "networking")]
use tokio::net::TcpStream;
// 组合特性
#[cfg(all(feature = "encryption", feature = "networking"))]
fn secure_network_send() {
// 需要同时启用两个特性
}
逻辑组合
// all() - 所有条件都必须满足(AND)
#[cfg(all(unix, target_pointer_width = "64"))]
fn unix_64bit_only() {
// 仅在 64 位 Unix 系统上编译
}
// any() - 任一条件满足即可(OR)
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn linux_or_mac() {
println!("Running on Linux or macOS");
}
// not() - 条件不满足时(NOT)
#[cfg(not(target_env = "msvc"))]
fn non_msvc_compiler() {
// 不使用 MSVC 编译器时
}
// 复杂组合
#[cfg(all(
unix,
not(target_os = "macos"),
any(target_arch = "x86_64", target_arch = "aarch64")
))]
fn complex_condition() {
// Unix 系统 AND 非 macOS AND (x86_64 OR aarch64)
}
#[cfg_attr] - 条件属性
根据条件动态应用其他属性。
// 语法:#[cfg_attr(条件, 属性)]
// 仅在测试时派生 Debug
#[cfg_attr(test, derive(Debug))]
struct MyStruct {
data: Vec<u8>,
}
// 多个属性
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub struct Config {
pub name: String,
pub value: i32,
}
// 条件文档
#[cfg_attr(feature = "advanced", doc = "高级功能已启用")]
#[cfg_attr(not(feature = "advanced"), doc = "基础功能")]
pub fn process() {}
实际应用场景
1. 平台特定实现
pub struct FileManager;
impl FileManager {
#[cfg(unix)]
pub fn create_lock_file(&self, path: &str) -> std::io::Result<()> {
use std::os::unix::fs::PermissionsExt;
let file = std::fs::File::create(path)?;
let mut perms = file.metadata()?.permissions();
perms.set_mode(0o600); // Unix 特有的权限设置
std::fs::set_permissions(path, perms)?;
Ok(())
}
#[cfg(windows)]
pub fn create_lock_file(&self, path: &str) -> std::io::Result<()> {
// Windows 实现
std::fs::File::create(path)?;
Ok(())
}
}
2. 可选依赖
// Cargo.toml:
// [dependencies]
// serde = { version = "1.0", optional = true }
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Data {
pub value: i32,
}
impl Data {
#[cfg(feature = "serde")]
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
#[cfg(not(feature = "serde"))]
pub fn to_json(&self) -> String {
panic!("Serde feature not enabled");
}
}
3. 调试代码
struct Database {
#[cfg(debug_assertions)]
query_log: Vec<String>,
}
impl Database {
fn new() -> Self {
Database {
#[cfg(debug_assertions)]
query_log: Vec::new(),
}
}
fn execute(&mut self, query: &str) {
#[cfg(debug_assertions)]
{
self.query_log.push(query.to_string());
println!("Executing: {}", query);
}
// 实际执行逻辑
}
#[cfg(debug_assertions)]
fn print_log(&self) {
for (i, query) in self.query_log.iter().enumerate() {
println!("{}: {}", i, query);
}
}
}
4. 性能优化的不同实现
// 在不同平台使用不同的优化实现
pub fn calculate_hash(data: &[u8]) -> u64 {
#[cfg(all(target_arch = "x86_64", target_feature = "sse4.2"))]
{
// 使用 SSE4.2 指令集的快速实现
unsafe { hash_sse42(data) }
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "sse4.2")))]
{
// 通用的慢速实现
hash_generic(data)
}
}
cfg! 宏 - 运行时检查
与 #[cfg] 不同,cfg! 在运行时返回布尔值:
fn main() {
// 编译时所有分支都存在,运行时选择
if cfg!(target_os = "windows") {
println!("Running on Windows");
} else if cfg!(target_os = "linux") {
println!("Running on Linux");
}
// 但是编译器优化后,未使用的分支会被删除
}
// 对比:#[cfg] 编译时就删除了代码
#[cfg(target_os = "windows")]
fn windows_only() {
// Linux 下编译时此函数根本不存在
}
最佳实践
- 优先使用
#[cfg]而不是运行时if检查 - 为所有平台提供实现,避免某些平台编译失败
- 测试多个配置:
cargo test --all-features - 文档化特性:说明哪些特性是可选的
- 避免过度条件化:太多
#[cfg]会使代码难以维护
常见陷阱
// 错误:可能在某些平台上缺少函数
#[cfg(target_os = "linux")]
pub fn get_user_name() -> String {
// 仅 Linux 实现
}
// 正确:所有平台都有实现
#[cfg(target_os = "linux")]
pub fn get_user_name() -> String {
// Linux 实现
}
#[cfg(not(target_os = "linux"))]
pub fn get_user_name() -> String {
// 其他平台实现
}
代码生成属性
#[derive] - 自动实现 Trait 详解
什么是 Derive?
#[derive] 允许编译器为你的类型自动生成 trait 的实现代码,避免手写重复的样板代码。这是 Rust 最常用的属性之一。
// 手动实现 Debug
struct Point {
x: i32,
y: i32,
}
impl std::fmt::Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
// 使用 derive 自动生成相同的代码
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
标准库可派生的 Trait
1. Debug - 格式化调试输出
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
fn main() {
let user = User { name: "Alice".to_string(), age: 30 };
println!("{:?}", user); // User { name: "Alice", age: 30 }
println!("{:#?}", user); // 美化输出
}
// 要求:所有字段都必须实现 Debug
2. Clone - 深拷贝
#[derive(Clone)]
struct Data {
values: Vec<i32>,
}
fn main() {
let data1 = Data { values: vec![1, 2, 3] };
let data2 = data1.clone(); // 完整复制,包括 Vec 的堆数据
}
// 要求:所有字段都必须实现 Clone
// 性能:可能涉及堆分配,较慢
3. Copy - 按位复制
#[derive(Copy, Clone)] // Copy 需要 Clone
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // 按位复制,p1 仍然有效
println!("{}, {}", p1.x, p2.x); // 都可以使用
}
// 要求:
// 1. 所有字段都必须实现 Copy
// 2. 不能包含 String, Vec 等拥有堆数据的类型
// 3. 必须同时 derive Clone
4. PartialEq 和 Eq - 相等性比较
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
assert_eq!(p1, p2); // 逐字段比较
}
// PartialEq: 允许部分相等(如浮点数 NaN != NaN)
#[derive(PartialEq)]
struct FloatPoint {
x: f64,
y: f64,
}
// Eq: 完全相等(自反性、对称性、传递性)
#[derive(PartialEq, Eq)]
struct IntPoint {
x: i32,
y: i32,
}
// 规则:Eq 需要 PartialEq,且不能包含浮点数
5. PartialOrd 和 Ord - 排序比较
#[derive(PartialEq, PartialOrd)]
struct Person {
age: u32,
name: String,
}
fn main() {
let p1 = Person { age: 30, name: "Alice".to_string() };
let p2 = Person { age: 25, name: "Bob".to_string() };
// 按字段顺序比较:先 age,后 name
assert!(p1 > p2); // 30 > 25
}
// Ord: 完全排序
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Priority {
level: u32,
}
// 规则:
// - PartialOrd 需要 PartialEq
// - Ord 需要 Eq + PartialOrd
// - 比较顺序:按字段定义顺序
6. Hash - 哈希值计算
use std::collections::HashMap;
#[derive(Hash, PartialEq, Eq)]
struct Key {
id: u64,
category: String,
}
fn main() {
let mut map = HashMap::new();
let key = Key { id: 1, category: "user".to_string() };
map.insert(key, "value");
}
// 要求:通常需要同时实现 PartialEq 和 Eq
// 规则:相等的值必须有相同的哈希值
7. Default - 默认值构造
#[derive(Default)]
struct Config {
timeout: u32, // 0
enabled: bool, // false
name: String, // ""
}
fn main() {
let config = Config::default();
assert_eq!(config.timeout, 0);
}
// 自定义某些字段的默认值
#[derive(Default)]
struct Settings {
#[default] // 需要 nightly 或使用 Default trait
port: u16, // 默认 0
// 或者手动实现
}
// 手动指定默认值
struct CustomConfig {
timeout: u32,
}
impl Default for CustomConfig {
fn default() -> Self {
CustomConfig { timeout: 30 }
}
}
多个 Trait 组合
// 常见组合
#[derive(Debug, Clone, PartialEq)]
struct Basic {
value: i32,
}
// 适合作为 HashMap 键
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct HashKey {
id: u64,
}
// 适合排序
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Sortable {
priority: u32,
}
// 简单数据类型全套
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct SimpleData {
value: i32,
}
派生条件和限制
// 可以派生 Copy(所有字段都是 Copy)
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
// 不能派生 Copy(Vec 不是 Copy)
// #[derive(Copy, Clone)]
struct Data {
values: Vec<i32>, // 错误!
}
// 可以派生 Clone
#[derive(Clone)]
struct Data {
values: Vec<i32>,
}
// 不能派生 Eq(包含浮点数)
// #[derive(Eq)]
struct FloatData {
value: f64, // 错误!f64 不实现 Eq
}
自定义派生(通过过程宏)
// 使用第三方库的 derive
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct User {
name: String,
age: u32,
}
// 自定义派生行为
#[derive(Serialize, Deserialize)]
struct Custom {
#[serde(rename = "userName")]
name: String,
#[serde(skip)]
internal_id: u64,
#[serde(default)]
enabled: bool,
}
派生宏的工作原理
// derive 实际生成的代码(简化版)
#[derive(Debug)]
struct Point { x: i32, y: i32 }
// 展开后大致等价于:
struct Point { x: i32, y: i32 }
impl std::fmt::Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
控制派生行为
// 忽略某些字段
#[derive(Debug)]
struct User {
name: String,
#[debug(skip)] // 某些库支持
password: String,
}
// 自定义比较顺序
#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Task {
priority: u32, // 先比较优先级
name: String, // 再比较名称
}
// 如果需要不同顺序,手动实现
impl Ord for Task {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// 自定义比较逻辑
self.name.cmp(&other.name)
}
}
性能考虑
// Clone 性能
#[derive(Clone)]
struct Large {
data: Vec<u8>, // 需要分配堆内存
buffer: [u8; 1024], // 复制大块栈内存
}
// 考虑使用 Rc/Arc 避免深拷贝
use std::rc::Rc;
#[derive(Clone)]
struct Shared {
data: Rc<Vec<u8>>, // 只复制引用计数
}
// Hash 性能
#[derive(Hash)]
struct Key {
// 前面的字段更重要,差异性大的字段放前面
id: u64,
category: String,
}
最佳实践
- 按需派生:不要盲目派生所有 trait
- Debug 几乎总是有用的:便于调试
- Copy vs Clone:小型栈数据优先 Copy
- HashMap 键:需要
Hash + Eq - 排序:需要
Ord或PartialOrd - 公共 API:考虑派生更多 trait 提高可用性
常见错误
// 错误 1: Copy 需要 Clone
// #[derive(Copy)] // 错误
#[derive(Copy, Clone)] // 正确
struct Point { x: i32 }
// 错误 2: Eq 需要 PartialEq
// #[derive(Eq)] // 错误
#[derive(PartialEq, Eq)] // 正确
struct Data { value: i32 }
// 错误 3: Ord 需要 PartialOrd + Eq + PartialEq
#[derive(PartialEq, Eq, PartialOrd, Ord)] // 正确顺序
struct Ordered { value: i32 }
测试相关属性
#[test] - 单元测试详解
基本用法
#[test] 标记函数为测试函数,由 cargo test 运行。
#[test]
fn test_addition() {
let result = 2 + 2;
assert_eq!(result, 4);
}
#[test]
fn test_subtraction() {
assert_eq!(5 - 3, 2);
}
// 测试函数通常返回 () 或 Result<(), E>
#[test]
fn test_with_result() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("Math is broken!"))
}
}
#[should_panic] - 预期 Panic
测试代码应该 panic 的情况。
#[test]
#[should_panic]
fn test_divide_by_zero() {
let _result = 1 / 0; // 应该 panic
}
// 指定预期的 panic 消息
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_out_of_bounds() {
let v = vec![1, 2, 3];
v[99]; // panic 消息必须包含 "index out of bounds"
}
// 部分匹配
#[test]
#[should_panic(expected = "overflow")]
fn test_overflow() {
let x: u8 = 255;
let _y = x + 1; // debug 模式下会 panic
}
#[ignore] - 忽略测试
标记测试默认不运行(例如耗时测试、外部依赖测试)。
#[test]
#[ignore]
fn expensive_test() {
// 这个测试很慢,默认跳过
std::thread::sleep(std::time::Duration::from_secs(10));
}
#[test]
#[ignore = "需要网络连接"]
fn test_network() {
// 需要外部资源
}
// 运行被忽略的测试:
// cargo test -- --ignored
// 运行所有测试(包括被忽略的):
// cargo test -- --include-ignored
组合使用
#[test]
#[ignore]
#[should_panic]
fn ignored_panic_test() {
// 被忽略的、预期会 panic 的测试
panic!("This is fine");
}
// 条件测试
#[test]
#[cfg(target_os = "linux")]
fn linux_only_test() {
// 仅在 Linux 上运行
}
#[test]
#[cfg(not(target_env = "msvc"))]
fn non_msvc_test() {
// 非 MSVC 编译器时运行
}
测试组织
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
#[cfg(test)]
mod tests {
use super::*;
// 分组测试
mod addition_tests {
use super::*;
#[test]
fn test_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_negative() {
assert_eq!(add(-2, -3), -5);
}
}
mod multiplication_tests {
use super::*;
#[test]
fn test_basic() {
assert_eq!(multiply(3, 4), 12);
}
}
}
#[cfg(test)] - 测试专用代码
仅在运行测试时编译的代码。
// 主代码
pub struct Database {
connection: String,
}
impl Database {
pub fn new(conn: String) -> Self {
Database { connection: conn }
}
pub fn query(&self, sql: &str) -> Vec<String> {
// 实际查询逻辑
vec![]
}
}
// 仅测试时存在的代码
#[cfg(test)]
impl Database {
// 测试专用的构造函数
pub fn new_test() -> Self {
Database {
connection: "test://localhost".to_string(),
}
}
// 测试辅助方法
pub fn insert_test_data(&mut self) {
// 插入测试数据
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_database() {
let mut db = Database::new_test(); // 仅在测试中可用
db.insert_test_data();
let results = db.query("SELECT * FROM users");
assert!(!results.is_empty());
}
}
// 测试辅助函数
#[cfg(test)]
fn create_test_user() -> User {
User {
name: "Test User".to_string(),
age: 30,
}
}
断言宏详解
#[test]
fn test_assertions() {
// assert! - 条件为真
assert!(2 + 2 == 4);
assert!(true, "可选的失败消息");
// assert_eq! - 相等断言
assert_eq!(2 + 2, 4);
assert_eq!(vec![1, 2], vec![1, 2], "向量应该相等");
// assert_ne! - 不等断言
assert_ne!(2 + 2, 5);
// debug_assert! - 仅 Debug 模式检查
debug_assert!(expensive_check(), "仅在 Debug 模式验证");
}
#[test]
#[should_panic(expected = "assertion failed")]
fn test_failing_assertion() {
assert_eq!(2 + 2, 5, "数学不对劲");
}
基准测试 #[bench]
仅在 nightly 编译器可用。
#![feature(test)]
extern crate test;
#[bench]
fn bench_addition(b: &mut test::Bencher) {
b.iter(|| {
let _result = 2 + 2;
});
}
#[bench]
fn bench_vector_creation(b: &mut test::Bencher) {
b.iter(|| {
let v: Vec<i32> = (0..1000).collect();
test::black_box(v); // 防止编译器优化掉
});
}
// 运行基准测试:
// cargo +nightly bench
集成测试
// tests/integration_test.rs(项目根目录的 tests 文件夹)
use my_crate::*;
#[test]
fn test_public_api() {
let result = add(2, 3);
assert_eq!(result, 5);
}
#[test]
#[ignore]
fn test_expensive_integration() {
// 耗时的集成测试
}
测试最佳实践
// 1. 命名清晰
#[test]
fn test_add_positive_numbers_returns_sum() {
assert_eq!(add(2, 3), 5);
}
// 2. 一个测试一个概念
#[test]
fn test_add_handles_zero() {
assert_eq!(add(0, 5), 5);
assert_eq!(add(5, 0), 5);
}
// 3. 测试边界情况
#[test]
fn test_add_max_values() {
assert_eq!(add(i32::MAX, 0), i32::MAX);
}
#[test]
#[should_panic]
fn test_add_overflow() {
let _result = add(i32::MAX, 1);
}
// 4. 使用辅助函数减少重复
#[cfg(test)]
fn setup_test_environment() -> TestContext {
TestContext::new()
}
#[test]
fn test_with_setup() {
let ctx = setup_test_environment();
// 使用 ctx 进行测试
}
// 5. 文档测试
/// 加法函数
///
/// # Examples
///
/// ```
/// use my_crate::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
运行测试的选项
# 运行所有测试
cargo test
# 运行特定测试
cargo test test_addition
# 运行特定模块的测试
cargo test tests::addition_tests
# 显示测试输出(println! 等)
cargo test -- --nocapture
# 并行运行测试(指定线程数)
cargo test -- --test-threads=4
# 单线程运行
cargo test -- --test-threads=1
# 运行被忽略的测试
cargo test -- --ignored
# 运行所有测试(包括被忽略的)
cargo test -- --include-ignored
# 仅运行文档测试
cargo test --doc
# 运行集成测试
cargo test --test integration_test
条件测试编译
#[test]
#[cfg(feature = "expensive-tests")]
fn expensive_feature_test() {
// 仅在启用特性时运行
}
// Cargo.toml:
// [features]
// expensive-tests = []
// 运行:cargo test --features expensive-tests
代码警告控制
#[allow], #[warn], #[deny], #[forbid] - Lint 控制详解
Lint 级别说明
Rust 编译器有四个 lint 级别,从宽松到严格:
// 1. #[allow] - 允许,抑制警告
#[allow(unused_variables)]
fn example() {
let x = 5; // 不会警告
}
// 2. #[warn] - 警告,但允许编译
#[warn(non_snake_case)]
fn myFunction() { // 警告:应该用 my_function
// 编译通过,但显示警告
}
// 3. #[deny] - 拒绝,视为错误
#[deny(unsafe_code)]
fn dangerous() {
// unsafe { } // 编译错误!
}
// 4. #[forbid] - 禁止,且不能被覆盖
#[forbid(unsafe_code)]
mod strictly_safe {
// #[allow(unsafe_code)] // 无效!forbid 不能被覆盖
// fn cant_override() {
// unsafe { } // 仍然是错误
// }
}
#[allow] vs #[forbid] 的区别
// allow 可以被内部代码覆盖
#[allow(unused_variables)]
mod outer {
#[warn(unused_variables)] // 可以重新启用警告
fn inner() {
let x = 5; // 会警告
}
}
// forbid 不能被覆盖
#[forbid(unused_variables)]
mod strict {
// #[allow(unused_variables)] // 编译错误!
fn inner() {
let x = 5; // 错误
}
}
常见 Lint 类别
1. 代码质量相关
// 未使用的代码
#[allow(dead_code)]
fn unused_function() {
// 这个函数从未被调用
}
#[allow(unused_variables)]
fn example() {
let unused_var = 42;
}
#[allow(unused_imports)]
mod m {
use std::collections::HashMap; // 未使用
}
#[allow(unused_mut)]
fn foo() {
let mut x = 5; // 实际不需要 mut
println!("{}", x);
}
// 无法访问的代码
#[allow(unreachable_code)]
fn unreachable() {
return;
println!("永远不会执行");
}
2. 命名规范相关
// 函数/变量应该用 snake_case
#[allow(non_snake_case)]
fn myFunction() {} // 应该是 my_function
#[allow(non_snake_case)]
let myVariable = 42; // 应该是 my_variable
// 类型应该用 CamelCase
#[allow(non_camel_case_types)]
struct my_struct {} // 应该是 MyStruct
// 常量应该用 SCREAMING_SNAKE_CASE
#[allow(non_upper_case_globals)]
const myConstant: i32 = 42; // 应该是 MY_CONSTANT
// 完整示例
#[allow(non_snake_case, non_camel_case_types)]
mod legacy_code {
pub struct oldStruct {
pub someField: i32,
}
pub fn LegacyFunction() {}
}
3. 类型和安全相关
// 不安全代码
#[deny(unsafe_code)]
mod safe_module {
// unsafe { } // 错误!
}
// 允许不安全代码(显式声明意图)
#[allow(unsafe_code)]
fn low_level_operation() {
unsafe {
// 底层操作
}
}
// 类型推断失败
#[allow(type_alias_bounds)]
type MyType<T: Trait> = Box<T>; // 可能无效的 trait 约束
// 裸指针解引用
#[warn(unsafe_op_in_unsafe_fn)]
unsafe fn careful() {
let ptr = 0x1234 as *const i32;
let _x = *ptr; // 警告:不安全操作
}
4. 模式匹配相关
// 未处理所有情况
#[allow(unreachable_patterns)]
fn match_example(x: i32) {
match x {
_ => {},
1 => {}, // 永远不会匹配
}
}
// 非穷尽的模式
enum Color {
Red,
Green,
Blue,
}
#[allow(non_exhaustive_omitted_patterns)]
fn handle_color(c: Color) {
match c {
Color::Red => {},
// 缺少 Green 和 Blue
_ => {},
}
}
5. 文档和注释相关
// 缺少文档注释
#[warn(missing_docs)]
pub mod my_module {
/// 这个有文档
pub fn documented() {}
// #[allow(missing_docs)]
pub fn undocumented() {} // 会警告
}
// 全局启用
#![warn(missing_docs)]
// 无效的文档
#[allow(rustdoc::broken_intra_doc_links)]
/// 参见 [`NonExistent`] // 链接无效
pub fn example() {}
6. 性能和优化相关
// 可能的性能问题
#[allow(clippy::needless_collect)]
fn inefficient() {
let v: Vec<_> = (0..10).collect();
let sum: i32 = v.iter().sum(); // 可以直接 sum,不需要 collect
}
// 盒装切片可以简化
#[allow(clippy::box_collection)]
fn boxed() {
let _b: Box<Vec<i32>> = Box::new(vec![1, 2, 3]);
}
作用范围
// 1. 整个 crate
#![allow(unused_variables)] // 内部属性(注意感叹号)
// 2. 模块
#[allow(dead_code)]
mod my_module {
fn unused() {} // 允许
}
// 3. 单个函数
#[allow(unused_variables)]
fn my_function() {
let x = 5; // 允许
}
// 4. 单个代码块
fn example() {
#[allow(unused_variables)]
{
let x = 5; // 允许
}
let y = 10; // 不允许(如果未使用)
}
// 5. 单个语句
fn statement_level() {
#[allow(unused_variables)]
let x = 5; // 允许
let y = 10; // 不允许(如果未使用)
}
组合多个 Lint
// 单个属性多个 lint
#[allow(dead_code, unused_variables, non_snake_case)]
fn complex_example() {
let UnusedVar = 42;
}
// 多个属性
#[allow(dead_code)]
#[allow(unused_variables)]
fn example() {}
// 不同级别组合
#[warn(unused_variables)]
#[deny(unsafe_code)]
mod my_module {}
Clippy Lints
Clippy 是 Rust 的 lint 工具,提供更多检查。
// 允许特定的 Clippy lint
#[allow(clippy::too_many_arguments)]
fn many_args(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {}
#[allow(clippy::single_match)]
fn single_match(x: Option<i32>) {
match x {
Some(_) => {},
None => {},
}
}
// 常见 Clippy lints
#[allow(clippy::unnecessary_wraps)]
fn always_ok() -> Result<i32, ()> {
Ok(42) // 永远不会失败
}
#[allow(clippy::len_without_is_empty)]
struct MyVec {
data: Vec<i32>,
}
impl MyVec {
fn len(&self) -> usize {
self.data.len()
}
// Clippy 建议:有 len() 就应该有 is_empty()
}
全局 Lint 配置
// src/lib.rs 或 src/main.rs 顶部
#![warn(missing_docs)]
#![warn(unused_imports)]
#![deny(unsafe_code)]
#![allow(dead_code)] // 开发阶段临时允许
// 分组管理
#![warn(
missing_docs,
unused_imports,
unused_variables,
)]
#![deny(
unsafe_code,
unstable_features,
)]
实际应用场景
1. 与外部 C 代码交互
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[repr(C)]
struct legacy_c_struct {
Field1: i32,
Field2: i32,
}
2. 生成的代码
#[allow(dead_code, unused_variables, non_snake_case)]
mod generated {
// 自动生成的代码,不需要警告
include!("generated.rs");
}
3. 开发中的代码
#[allow(dead_code, unused_variables)]
#[cfg(feature = "experimental")]
mod experimental {
// 实验性功能,还在开发中
pub fn work_in_progress() {
let placeholder = 42;
}
}
4. 向后兼容
#[deprecated(since = "1.2.0", note = "使用 new_function 代替")]
#[allow(dead_code)]
pub fn old_function() {
// 保留旧 API 用于向后兼容
}
最佳实践
// 好的做法:范围最小化
fn example() {
#[allow(unused_variables)]
let temp = expensive_computation(); // 仅这一行允许
// 其他代码仍然检查
}
// 避免:过度使用
#[allow(warnings)] // 禁用所有警告,太宽泛
fn bad_example() {}
// 好的做法:添加注释说明原因
#[allow(unsafe_code)] // 需要直接操作内存以提高性能
unsafe fn optimize() {}
// 好的做法:开发时临时允许,发布前移除
#[cfg(debug_assertions)]
#[allow(dead_code)]
fn debug_helper() {}
查看所有可用的 Lints
# 查看编译器 lints
rustc -W help
# 查看 Clippy lints
cargo clippy -- -W help
# 运行 Clippy
cargo clippy
# Clippy 更严格模式
cargo clippy -- -W clippy::pedantic
Lint 组
// warnings - 所有警告
#![deny(warnings)] // 将所有警告视为错误
// nonstandard_style - 命名规范
#![warn(nonstandard_style)]
// rust-2018-idioms - Rust 2018 习惯用法
#![warn(rust_2018_idioms)]
// unused - 所有"未使用"类警告
#![warn(unused)]
文档属性
#[doc]
为代码添加文档注释。
/// 这是一个文档注释(等同于 #[doc = "..."])
///
/// # Examples
///
/// ```
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[doc(hidden)]
pub fn internal_function() {
// 在文档中隐藏
}
内联和优化属性
#[inline] - 函数内联详解
什么是内联?
内联(Inlining) 是编译器的一种优化技术,它将函数调用替换为函数体本身的代码。简单来说,就是把函数的代码直接"复制粘贴"到调用它的地方,而不是通过传统的函数调用机制。
示例对比:
// 未内联的代码
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let result = add(5, 3); // 函数调用开销:压栈、跳转、返回
}
// 内联后的等效代码(编译器优化后)
fn main() {
let result = 5 + 3; // 直接执行,无函数调用开销
}
内联的优点
- 消除函数调用开销
- 避免参数压栈/出栈
- 避免跳转指令
- 避免返回地址保存和恢复
- 对于小型函数,这些开销可能比函数体本身还大
- 启用更多优化机会
#[inline] fn square(x: i32) -> i32 { x * x } fn compute() -> i32 { square(5) // 编译器能看到 x = 5,可以编译时计算 } // 内联后,编译器可以直接优化为 25 - 提高缓存友好性
- 减少指令跳转,提高 CPU 指令缓存命中率
- 代码局部性更好
- 允许常量折叠和死代码消除
#[inline] fn get_debug_level() -> u32 { if cfg!(debug_assertions) { 2 } else { 0 } } fn log(msg: &str) { if get_debug_level() > 0 { // 内联后,release 模式此分支可被完全删除 println!("{}", msg); } }
内联的缺点
- 代码膨胀(Code Bloat)
#[inline] fn large_function() { // 100 行代码... } fn caller1() { large_function(); } // 复制 100 行 fn caller2() { large_function(); } // 再复制 100 行 fn caller3() { large_function(); } // 又复制 100 行 // 二进制文件大小显著增加- 每个调用点都复制一份函数体
- 增加二进制文件大小
- 可能超出指令缓存容量,反而降低性能
- 编译时间增加
- 编译器需要在每个调用点进行优化分析
- 大量内联会显著延长编译时间
- 破坏代码局部性
- 过度内联可能导致热点代码分散
- 影响 CPU 缓存效率
- 调试困难
- 内联后的代码在调试器中难以追踪
- 堆栈跟踪可能不准确
- 不利于动态库
- 内联的函数无法在运行时更新
- 库更新时需要重新编译依赖项
内联变体详解
// 1. #[inline] - 提示编译器考虑内联
#[inline]
pub fn suggested_inline() -> i32 {
42
}
// 跨 crate 调用时必须标记,否则编译器看不到函数体
// 编译器可以忽略此提示
// 2. #[inline(always)] - 强制内联(除非不可能)
#[inline(always)]
pub fn forced_inline() -> i32 {
100
}
// 警告:可能导致代码膨胀
// 适用于:性能关键的微小函数
// 3. #[inline(never)] - 禁止内联
#[inline(never)]
pub fn no_inline() -> i32 {
200
}
// 适用于:调试、性能分析、减少代码膨胀
// 4. 无属性 - 编译器自行决定
pub fn auto_inline() -> i32 {
300
}
// 同一 crate 内:编译器可能自动内联
// 跨 crate:默认不内联(看不到函数体)
何时使用内联
应该使用 #[inline] 的场景:
- 小型函数(1-3 行)
#[inline] pub fn is_empty(&self) -> bool { self.len == 0 } - Getter/Setter 方法
#[inline] pub fn get_x(&self) -> i32 { self.x } - 性能关键的热点路径
#[inline] pub fn fast_hash(data: &[u8]) -> u64 { // 频繁调用的哈希函数 } - 泛型函数(几乎总是应该内联)
#[inline] pub fn max<T: Ord>(a: T, b: T) -> T { if a > b { a } else { b } } // 内联后可以针对具体类型优化 - 跨 crate 的公共小函数
#[inline] pub fn add_one(x: i32) -> i32 { x + 1 } // 没有 #[inline],外部 crate 无法内联
不应该使用的场景:
- 大型函数(>10 行或包含循环)
- 很少被调用的函数
- 递归函数(编译器通常无法内联)
- 已经足够快的函数
实际性能案例
use std::time::Instant;
// 案例 1:微小函数 - 内联受益明显
#[inline]
fn add_inline(a: i32, b: i32) -> i32 {
a + b
}
fn add_no_inline(a: i32, b: i32) -> i32 {
a + b
}
// 案例 2:中等函数 - 权衡利弊
#[inline(never)] // 为了对比,禁止内联
fn complex_no_inline(x: i32) -> i32 {
let mut result = x;
for i in 0..10 {
result = result.wrapping_mul(i).wrapping_add(7);
}
result
}
#[inline]
fn complex_inline(x: i32) -> i32 {
let mut result = x;
for i in 0..10 {
result = result.wrapping_mul(i).wrapping_add(7);
}
result
}
// 基准测试显示:
// - add_inline: 快 2-3 倍(函数调用开销占比大)
// - complex_inline: 快 5-10%(函数体执行时间占主导)
编译器的智能决策
// Rust 编译器在以下情况自动内联(即使没有 #[inline]):
// 1. 同一编译单元内的私有函数
fn internal_helper() -> i32 {
42
}
// 2. LTO(链接时优化)开启时
// Cargo.toml: lto = true
// 3. 明显有益的情况(编译器能自行判断)
最佳实践建议
- 默认不加
#[inline],让编译器决定 - 公共 API 的小函数(<5 行)添加
#[inline] - 性能敏感代码先用性能分析工具测量,再决定
- 避免
#[inline(always)],除非有明确证据 - 泛型函数几乎总是需要
#[inline]
验证内联效果
# 查看汇编代码,确认是否内联
cargo rustc --release -- --emit asm
# 使用 cargo-asm 查看特定函数
cargo install cargo-asm
cargo asm --release my_crate::my_function
# 性能基准测试
cargo bench
功能开关属性
#[feature]
启用不稳定的实验性功能(仅限 nightly 编译器)。
#![feature(const_fn_floating_point_arithmetic)]
#![feature(never_type)]
模块和可见性属性
#[path]
指定模块文件的路径。
#[path = "custom/path/to/module.rs"]
mod my_module;
宏相关属性
#[macro_use] 和 #[macro_export]
// 导入外部 crate 的宏
#[macro_use]
extern crate serde_derive;
// 导出宏供其他 crate 使用
#[macro_export]
macro_rules! my_macro {
() => { println!("Hello!"); }
}
内存布局属性
#[repr] - 内存表示详解
什么是 repr?
#[repr] 控制类型在内存中的布局方式。Rust 默认不保证结构体的内存布局,这允许编译器优化,但在与 C 代码交互或进行底层编程时,需要精确控制内存布局。
#[repr(Rust)] - 默认布局
// 默认布局(不需要显式标注)
struct DefaultLayout {
a: u8, // 1 字节
b: u32, // 4 字节
c: u16, // 2 字节
}
// Rust 可能重排字段以减少内存占用和提高对齐:
// 实际内存布局可能是:
// b: u32 (偏移 0, 4字节)
// c: u16 (偏移 4, 2字节)
// a: u8 (偏移 6, 1字节)
// + 1 字节填充 = 总共 8 字节
// 字段顺序不影响功能,但影响内存布局
#[repr(C)] - C 语言兼容布局
用于与 C 代码交互,保证字段按声明顺序排列。
#[repr(C)]
struct Point {
x: i32, // 偏移 0
y: i32, // 偏移 4
}
// 总大小: 8 字节,与 C 的 struct Point 完全兼容
#[repr(C)]
struct MixedSize {
a: u8, // 偏移 0 (1 字节)
// 3 字节填充,对齐到 4 字节边界
b: u32, // 偏移 4 (4 字节)
c: u16, // 偏移 8 (2 字节)
// 2 字节填充,对齐到 4 字节边界
}
// 总大小: 12 字节
// FFI 使用示例
#[repr(C)]
struct Config {
enabled: bool,
timeout: u32,
name: [u8; 32],
}
extern "C" {
fn process_config(config: *const Config);
}
fn main() {
let cfg = Config {
enabled: true,
timeout: 30,
name: [0; 32],
};
unsafe {
process_config(&cfg);
}
}
#[repr(C)] 枚举
// C 风格枚举
#[repr(C)]
enum Status {
Ok = 0,
Error = 1,
Pending = 2,
}
// 带数据的枚举(tagged union)
#[repr(C)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
// 指定判别值类型
#[repr(C, u8)]
enum SmallEnum {
A, // 0
B, // 1
C, // 2
}
// 判别值用 u8 存储,节省空间
#[repr(transparent)] - 透明包装
只有一个非零大小字段的包装类型,内存布局与内部字段完全相同。
#[repr(transparent)]
struct Wrapper(u32);
// 在内存中,Wrapper 和 u32 完全一样
// 可以安全地在 FFI 边界传递
#[repr(transparent)]
struct UserId(u64);
#[repr(transparent)]
struct Meters(f64);
// 用于类型安全,同时保持二进制兼容
extern "C" {
fn process_id(id: u64);
}
fn safe_process(user: UserId) {
unsafe {
// UserId 和 u64 布局完全相同
process_id(user.0);
}
}
// 错误:多个非零大小字段
// #[repr(transparent)]
// struct Invalid(u32, u64); // 编译错误
// 正确:可以有多个零大小字段
#[repr(transparent)]
struct Valid<T>(T, PhantomData<String>);
#[repr(packed)] - 紧凑布局
移除字段之间的填充,减小结构体大小,但可能导致未对齐访问。
#[repr(packed)]
struct Packed {
a: u8, // 偏移 0
b: u32, // 偏移 1 (未对齐!)
c: u16, // 偏移 5
}
// 总大小: 7 字节(无填充)
// 对比:不使用 packed
struct Normal {
a: u8, // 偏移 0
// 3 字节填充
b: u32, // 偏移 4
c: u16, // 偏移 8
// 2 字节填充
}
// 总大小: 12 字节
// ⚠️ 警告:访问未对齐字段很危险
#[repr(packed)]
struct Dangerous {
x: u8,
y: u64, // 可能未对齐
}
fn use_packed() {
let d = Dangerous { x: 1, y: 2 };
// ❌ 错误:创建未对齐引用是未定义行为
// let y_ref = &d.y;
// ✅ 正确:使用 ptr::addr_of!
let y_value = unsafe {
std::ptr::addr_of!(d.y).read_unaligned()
};
}
#[repr(packed(N))] - 指定对齐
// 对齐到 2 字节边界
#[repr(packed(2))]
struct Packed2 {
a: u8, // 偏移 0
// 1 字节填充
b: u32, // 偏移 2 (2字节对齐)
c: u16, // 偏移 6
}
// 总大小: 8 字节
#[repr(packed(4))]
struct Packed4 {
a: u8, // 偏移 0
// 3 字节填充
b: u32, // 偏移 4
c: u16, // 偏移 8
}
#[repr(align(N))] - 指定最小对齐
强制类型对齐到指定字节边界。
// 对齐到 16 字节边界(SIMD 优化)
#[repr(align(16))]
struct Aligned {
data: [u8; 12],
}
// 缓存行对齐(64 字节)
#[repr(align(64))]
struct CacheLine {
data: [u8; 64],
}
// 组合使用
#[repr(C, align(8))]
struct Combined {
x: u32,
y: u32,
}
// 实际应用:避免伪共享
#[repr(align(64))]
struct Counter {
value: AtomicU64,
// 填充到 64 字节,确保每个 Counter 独占一个缓存行
}
整数表示 #[repr(u8)], #[repr(i32)] 等
指定枚举的判别值类型。
// 默认使用 isize
enum Default {
A, B, C,
}
// 使用 u8(节省空间)
#[repr(u8)]
enum Small {
A = 0,
B = 1,
C = 2,
}
// 使用 u32
#[repr(u32)]
enum Medium {
Low = 100,
High = 200,
}
// 实际应用:网络协议
#[repr(u16)]
enum PacketType {
Data = 0x0001,
Ack = 0x0002,
Error = 0x0003,
}
fn serialize(packet_type: PacketType) -> [u8; 2] {
let value = packet_type as u16;
value.to_le_bytes()
}
组合不同的 repr
// C 布局 + u8 判别值
#[repr(C, u8)]
enum CEnum {
A,
B,
C,
}
// C 布局 + 对齐
#[repr(C, align(8))]
struct Aligned {
x: u32,
}
// 错误:不能同时使用 packed 和 align
// #[repr(packed, align(8))]
// struct Invalid {}
// 错误:transparent 只能单独使用
// #[repr(transparent, C)]
// struct Invalid(u32);
实际应用场景
1. FFI (Foreign Function Interface)
// 与 C 库交互
#[repr(C)]
struct FileInfo {
size: u64,
modified: u64,
is_dir: bool,
}
#[link(name = "mylib")]
extern "C" {
fn get_file_info(path: *const u8) -> FileInfo;
}
2. 网络协议
#[repr(C, packed)]
struct TcpHeader {
src_port: u16,
dst_port: u16,
seq_num: u32,
ack_num: u32,
flags: u16,
window: u16,
checksum: u16,
urgent: u16,
}
3. 内存映射 I/O
#[repr(C)]
struct DeviceRegisters {
control: u32,
status: u32,
data: u32,
}
// 映射到固定内存地址
const DEVICE_BASE: usize = 0x4000_0000;
let registers = unsafe {
&mut *(DEVICE_BASE as *mut DeviceRegisters)
};
4. SIMD 优化
#[repr(align(16))]
struct Vec4 {
data: [f32; 4],
}
// 确保 16 字节对齐,适用于 SSE 指令
5. 无锁数据结构
// 避免伪共享
#[repr(align(64))]
struct PaddedAtomic {
value: AtomicU64,
}
struct LockFreeQueue {
head: PaddedAtomic, // 独占缓存行
tail: PaddedAtomic, // 独占缓存行
}
查看内存布局
use std::mem::{size_of, align_of};
#[repr(C)]
struct Example {
a: u8,
b: u32,
c: u16,
}
fn main() {
println!("Size: {}", size_of::<Example>()); // 12
println!("Align: {}", align_of::<Example>()); // 4
println!("Offset of a: {}", offset_of!(Example, a)); // 需要 memoffset crate
println!("Offset of b: {}", offset_of!(Example, b));
println!("Offset of c: {}", offset_of!(Example, c));
}
最佳实践
- 默认使用 Rust 布局:让编译器优化
- FFI 使用
#[repr(C)]:与 C 代码交互时必需 - 谨慎使用
packed:未对齐访问可能导致性能问题或崩溃 transparent用于零成本抽象:newtype 模式- 性能关键代码考虑
align:SIMD、缓存优化
常见陷阱
// 错误:未对齐访问
#[repr(packed)]
struct Bad {
x: u8,
y: u64,
}
fn broken(b: &Bad) {
let _y = b.y; // 可能崩溃或产生错误结果
}
// 正确:使用 read_unaligned
fn correct(b: &Bad) {
let y = unsafe {
std::ptr::addr_of!(b.y).read_unaligned()
};
}
// 错误:transparent 要求恰好一个非零大小字段
// #[repr(transparent)]
// struct Invalid(u32, u64);
// 正确
#[repr(transparent)]
struct Valid(u32);
弃用属性
#[deprecated]
标记代码为已弃用。
#[deprecated]
fn old_function() {}
#[deprecated(since = "1.2.0", note = "请使用 new_function 代替")]
fn legacy_function() {
// ...
}
标准库和外部 crate 常用属性
Serde(序列化)
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct User {
#[serde(rename = "username")]
name: String,
#[serde(skip)]
password: String,
#[serde(default)]
active: bool,
}
异步相关
// Tokio
#[tokio::main]
async fn main() {
// 异步主函数
}
#[tokio::test]
async fn test_async() {
// 异步测试
}
条件编译实用示例
// 根据特性开关编译
#[cfg(feature = "advanced")]
mod advanced_features;
// 组合条件
#[cfg(all(unix, target_pointer_width = "64"))]
fn unix_64bit_only() {}
#[cfg(any(windows, target_os = "macos"))]
fn windows_or_mac() {}
#[cfg(not(debug_assertions))]
fn release_only() {}
最佳实践
- 合理使用
#[inline]:过度使用可能导致代码膨胀 - 文档注释:为公共 API 添加详细文档
- 条件编译:用于平台特定代码和可选功能
- 派生 trait:尽可能使用
#[derive]减少样板代码 - 警告控制:谨慎使用
#[allow],不要隐藏真正的问题