Skip to content

Rust 工程补充:转换、宏、条件编译与版本边界 ​

前置:trait与模块测试。旧Book第一版混合了稳定特性、当时的nightly实验和实现细节,本篇把可复用能力组织为stable工程。下文Windows/Linux执行记录属于此前批次;本轮示例未执行,仅静态审读,命令和断言是未来自跑规格。

完整工具实验 ​

全部源码在 concurrency-lab 的 examples/toolbox.rs,项目Cargo.toml见项目页。在项目目录执行 cargo run --offline --example toolbox,所有断言通过并输出 toolbox ok。

rs
use std::{borrow::Borrow, collections::HashMap, fmt};

#[derive(Debug, PartialEq)]
struct Positive(u32);
#[derive(Debug)]
struct Invalid;
impl fmt::Display for Invalid {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "positive integer required") }
}
impl std::error::Error for Invalid {}
impl TryFrom<i64> for Positive {
    type Error = Invalid;
    fn try_from(value: i64) -> Result<Self, Self::Error> {
        let n = u32::try_from(value).map_err(|_| Invalid)?;
        if n == 0 { Err(Invalid) } else { Ok(Self(n)) }
    }
}
macro_rules! count_items {
    ($($item:expr),* $(,)?) => { <[()]>::len(&[$({ let _ = &$item; () }),*]) };
}
const LIMIT: usize = 8;
static LABEL: &str = "toolbox";
fn lookup<'a>(map: &'a HashMap<String, usize>, key: &str) -> Option<&'a usize> {
    map.get(key) // String: Borrow<str>; equivalent Hash/Eq semantics
}
fn bytes<T: AsRef<[u8]>>(input: T) -> usize { input.as_ref().len() }
fn main() {
    assert_eq!(Positive::try_from(2).unwrap(), Positive(2));
    assert!(Positive::try_from(-1).is_err());
    assert!(Positive::try_from(i64::MAX).is_err());
    let words = HashMap::from([(String::from("rust"), 4)]);
    assert_eq!(lookup(&words, "rust"), Some(&4));
    let owned = String::from("rust");
    let view: &str = owned.borrow();
    assert_eq!(view, "rust");
    assert_eq!(bytes("文"), 3);
    assert_eq!(count_items![1, "two", owned,], 3);
    assert_eq!(LIMIT, 8);
    assert_eq!(LABEL, "toolbox");
    #[cfg(target_pointer_width = "64")]
    assert_eq!(std::mem::size_of::<usize>(), 8);
    let input = [1, 2, 3];
    let [head, rest @ ..] = input.as_slice() else { panic!("empty") };
    assert_eq!((*head, rest), (1, &[2, 3][..]));
    println!("toolbox ok");
}

转换、Borrow 与 AsRef ​

as 对整数做类型转换,窄化可截断,不能代替业务校验。From用于约定的无失败转换,TryFrom返回Result处理可能失败的转换;Positive先检查u32范围再检查非零,i64::MAX与负数都拒绝,不先强转丢失信息。类型别名不会增加这些不变量,newtype与构造入口才会。

AsRef表示低成本借用视图,适合泛型接收字符串/字节;Borrow要求借用形式与拥有形式在Eq/Ord/Hash语义上相容,支持HashMap<String,_>用&str查找。如果自定义不区分大小写的键,不能随意Borrow成默认区分大小写的str。Deref用于智能指针式行为,不建议为一般业务模型模拟继承。字符串len是字节数,"文"为3字节;chars是Unicode标量,不等于用户感知字形。

模式、常量、属性与 cfg ​

切片模式 [head, rest @ ..] 分解一个非空切片,空输入走else,不用先索引触发panic。match要求穷尽,if let/while let适合只处理一种成功模式;把所有失败折叠成通配项前先确认错误确实可忽略。const是可在使用点求值的常量,static表示程序级存储;不要用static mut共享计数,选择Mutex或原子类型。

属性如derive/test/cfg会影响编译,#[cfg(...)] 移除不匹配代码,cfg!(...) 则产生布尔值,两边代码仍需能通过类型检查。特性feature只表示依赖与构建配置,不是权限。条件分支各自应在对应目标上测试,本批执行Windows与Linux amd64;64位断言不证明32位或交叉目标已运行。Cargo.lock固定可重建的依赖解析,应用提交锁文件;换镜像应核对来源与校验,不能以快为由信任不明二进制。

宏、文档与模块 ​

macro_rules按token模式扩展,expr捕获表达式,*做重复,$(,)?允许尾逗号。示例宏让每个参数表达式求值一次,再借用结果构造unit数组并计算长度;宏并非任意字符串替换,也不应隐藏文件写入或重复副作用。简单复用优先函数,只有函数无法表达的语法生成再考虑宏。

过程宏分别有derive/attribute/function-like形式,在独立proc-macro crate中处理token;旧plugin_registrar/libsyntax/#![plugin]是已移除的旧机制,不能改个版本号继续用。本课程不实现编译器插件。文档注释///用于公开项,//!用于模块;rustdoc的Rust代码块通常成为文档测试,ignore会跳过,不能把跳过写成通过。crate是编译边界,module组织命名空间,pub控制可见性;宏展开不绕过私有性与借用规则。

历史实验的去向 ​

旧box语法/box patterns不作为stable教程,独占堆值用Box::new;旧关联常量feature gate已不需要。旧内联汇编语法与intrinsics内部接口不是稳定应用API;当前asm接口也依赖架构和安全证明。本批不提供未执行的汇编或FFI库调用。no_std不表示“没有任何库”,可使用core并按环境配置alloc、分配器与panic处理;嵌入式启动/链接脚本需要具体目标,不用桌面项目冒充验证。自制分配器、静态链接平台细节、FFI宿主集成和过程宏开发保留为专门领域边界,旧文入口明确说明未建立等价实验。

练习 ​

① 将Positive构造改成value as u32后检查非零,哪个测试可抓住截断?② 为什么Borrow契约比AsRef强?③ cfg!(false)里的错误方法为什么仍报错?④ 宏中把$item使用两次可能出什么问题?

参考解法 ​

① i64::MAX被截断成非零u32,原来必须拒绝的输入会成功;也可在i64中构造u32::MAX+2,它截断为1,能暴露先强转再检查非零的缺陷。u32::MAX+1截断为0,仍会被非零检查拒绝,不能独立捕获这个缺陷。② 借用查找必须在拥有键与借用键之间保持相同哈希/相等语义,否则会查不到;普通视图不承诺这些。③ 普通if两支仍参与类型检查,真正移除代码用cfg属性。④ 参数表达式可能有副作用或移动值,两次求值会改变结果或编译失败;保持求值次数明确。

来源 ​

2026-10-17核对官方中文学习入口、Borrow、TryFrom、条件编译、宏与过程宏。

继续阅读