趣味学RUST基础篇(高级特征)
章鱼小新的家族是忍者世家,小新掩人耳目的身份是一家火锅店老板,平时用的都是“基础忍术”——变量、函数、控制流,就像他每天吃零食、看动感超人一样普通。但有一天,他发现爸爸的书房里有本闪着金光的书,上面写着:“仅限忍者高手开启”——这就是《Rust 高级特性秘籍》!
第一页:暗影忍术——“不安全 Rust” (Unsafe Rust)
秘籍上画着一个戴着黑色面罩的小新,旁边写着:“此处危险!进入须谨慎!”
“当你需要突破常规忍术的限制时,可使用此术。但代价是,你必须自己保证安全,忍者大师(编译器)不再保护你!”
小新明白了:Rust 平时像一个超级严格的老师,时刻说“不许越界!不许空指针!”。但“不安全 Rust”就像打开了“开发者模式”,允许小新直接操作内存、调用 C 语言写的术法,或者做一些“危险动作”。
小新笔记:
“这招威力巨大,但一不小心就会‘Segmentation Fault’(忍术反噬)!除非万不得已,否则还是乖乖听话比较好。”
第二页:忍者契约进阶——“高级 Trait”
秘籍翻到“忍者契约”章节,小新发现契约还能这么玩!
-
关联类型(Associated Types):
就像给契约加了个“通用装备槽”。比如“忍者必须携带一种武器”,但没规定是手里剑还是苦无。每个忍者(实现者)自己决定带啥,但必须带一种。 -
默认参数(Default Type Parameters):
“如果没特别说明,就默认带手里剑。”——省事! -
完全限定语法(Fully Qualified Syntax):
当小新同时继承了“动感超人粉丝”和“蜡笔小新粉丝”两个契约,都叫watch_tv()
,怎么办?
这时就得说清楚:“我要用动感超人粉丝的watch_tv()
!”——这就是完全限定。 -
父契约(Supertraits):
“想成为‘高级忍者’,你必须先是个‘基础忍者’!”——契约也能有继承关系。 -
Newtype 模式:
小新把自己最喜欢的“巧克力饼干”包装成一个新类型YummyCookie
,这样他就不会和普通的“饼干”搞混了,还能给它加上专属技能,比如“自动飞向嘴巴”!
第三页:神秘符文——“高级类型”
秘籍上画满了奇怪的符文:
-
类型别名(Type Aliases):
给长名字的类型起个外号!比如把Result<String, Box<dyn Error>>
叫MyResult
,写起来省力多了。 -
永不返回的类型(Never Type
!
):
一种神秘的“黑洞”类型,代表“这招一出,世界终结”——比如panic!()
或无限循环。它不返回任何值,因为程序已经“死了”。 -
动态大小类型(DSTs):
有些类型大小在编译时不知道,比如str
(字符串切片)或Trait
。它们必须被“装进盒子里”(比如&str
或Box<dyn Trait>
)才能使用,就像把幽灵关进符咒里。
第四页:忍术卷轴——“高级函数与闭包”
-
函数指针(Function Pointers):
小新可以把一个忍术(函数)的名字记在卷轴上,然后让别人按卷轴执行。比如let f = 动感光波; f();
。 -
返回闭包:
更厉害的是,小新可以现场画一个“临时忍术卷轴”(闭包),然后把它交给别人用。但要注意,这个卷轴可能依赖现场的查克拉(变量),所以得用Box
包起来寄出去。
第五页:自动绘符术——“宏” (Macros)
这是秘籍最炫酷的一页!宏就像“自动绘符机器”。
小新只要说:“我要一个能打印三次‘动感超人’的术!”
然后机器“咔咔咔”自动画出:
println!("动感超人!");
println!("动感超人!");
println!("动感超人!");
宏在代码编译前就自动帮你生成代码,比函数更灵活,能做函数做不到的事。但它的语法有点像魔法咒语,学起来要花点功夫。
秘籍最后一页:忍者守则
“这些高级忍术,平时用得少,但关键时刻能救命。记住:
- 不安全代码,非必要不用。
- 宏,别滥用,否则代码会变得像乱码。
- 其他高级特性,是为了解决特定难题而生。
现在,你已窥见 Rust 的全貌。去吧,年轻的忍者,用这些知识,守护你的代码世界!”
小新合上秘籍,眼睛闪闪发亮:“原来 Rust 这么酷!下次写作业……不,下次写游戏,我要用‘宏’来自动生成一百个‘动感光波’!”
章鱼小新的“忍者契约”进阶课:高级 Trait 大揭秘!
小新最近迷上了“动感超人”和“蜡笔小新”两个粉丝俱乐部。这两个俱乐部都要求会员会“看动画片”和“收集卡片”。小新心想:“要是能一次加入两个俱乐部多好!”
于是,他打开了他的《Rust 忍者秘籍》,翻到了“高级忍者契约(Advanced Traits)”这一章。
第一课:专属装备槽——关联类型(Associated Types)
秘籍上画着一个“万能忍具袋”,上面写着:Item
。
“忍者契约可以声明一个‘装备槽’,但不指定具体带啥。每个忍者自己决定!”
trait 忍者装备 {type 武器; // 这就是“装备槽”!fn 使用武器(&self) -> Self::武器;
}// 小新选择带“动感光波”
struct 小新;
impl 忍者装备 for 小新 {type 武器 = String; // 装备槽里放“字符串”fn 使用武器(&self) -> String {"动感光波!!!".to_string()}
}// 爸爸选择带“扳手”
struct 爸爸;
impl 忍者装备 for 爸爸 {type 武器 = i32; // 装备槽里放“整数”(代表扳手数量)fn 使用武器(&self) -> i32 {1 // 挥一下扳手}
}fn main() {let 小新 = 小新;println!("小新使用武器:{}", 小新.使用武器());let 爸爸 = 爸爸;println!("爸爸使用武器:{}", 爸爸.使用武器());
}
小新笔记:
“太方便了!每个忍者都能用自己的武器,但都遵守同一个‘使用武器’的规矩。就像不同粉丝俱乐部,都能‘看动画’,但看的动画可能不同!”
第二课:默认选项——默认类型参数(Default Type Parameters)
小新发现,大多数忍者都喜欢带“手里剑”。秘籍说:
“可以在契约里写:‘如果没特别说明,就默认带手里剑!’”
trait 忍者装备<T = 手里剑> { // <T = 手里剑> 就是默认值!fn 投掷(&self, 武器: T);
}struct 手里剑;
这样,如果小新想用默认的手里剑,直接实现就行:
impl 忍者装备 for 小新 {fn 投掷(&self, _: 手里剑) {println!("嗖!手里剑飞出去!");}
}
但如果他想用香蕉皮当武器,也可以覆盖默认值:
impl 忍者装备<香蕉皮> for 小新 { // 明确指定 T 是 香蕉皮fn 投掷(&self, _: 香蕉皮) {println!("啪!敌人滑倒了!");}
}fn main() {//调用let 手里剑 = 手里剑;小新.投掷(手里剑);
}
小新笔记:
“默认选项真省事!大多数情况用默认的,特殊需求再改,完美!”
第三课:名字打架了怎么办?——完全限定语法(Fully Qualified Syntax)
问题来了!小新同时加入了“动感超人粉丝”和“蜡笔小新粉丝”两个俱乐部,两个俱乐部都要求会员会 watch_tv()
!
trait 动感超人粉丝 {fn watch_tv(&self) {println!("看动感超人!正义必胜!");}
}trait 蜡笔小新粉丝 {fn watch_tv(&self) {println!("看蜡笔小新!好开心!");}
}impl 动感超人粉丝 for 小新 {}
impl 蜡笔小新粉丝 for 小新 {}let 小新 = 小新;// 小新. watch_tv(); // 编译器懵了:到底看哪个?!
编译器:“我也不知道该执行哪个 watch_tv
啊!”
这时,秘籍教了小新“完全限定语法”——说出全名!
fn main() {// 明确指定:我要用“动感超人粉丝”这个契约的 watch_tv!<小新 as 动感超人粉丝>::watch_tv(&小新);// 或者,我要用“蜡笔小新粉丝”的!<小新 as 蜡笔小新粉丝>::watch_tv(&小新);
}
第四课:进阶忍者的门槛——父契约(Supertraits)
秘籍上写着:“想成为‘高级忍者’,你必须先是个‘基础忍者’!”
这就是“父契约”(Supertrait)。
// 先定义“基础忍者”契约
trait 基础忍者 {fn 跑酷(&self);
}// “高级忍者”契约说:想实现我,必须先实现“基础忍者”!
trait 高级忍者: 基础忍者 { // 冒号后面就是“父契约”!fn 隐身(&self);
}
这意味着,任何想成为“高级忍者”的人,必须先学会“跑酷”,再学“隐身”。
struct 忍者小新;// 必须先实现父契约!
impl 基础忍者 for 忍者小新 {fn 跑酷(&self) {println!("小新翻了个跟头!");}
}// 再实现高级契约
impl 高级忍者 for 忍者小新 {fn 隐身(&self) {println!("小新……消失了?(其实躲沙发底下了)");}
}
小新笔记:
“这就像游戏里,想学‘火球术’,必须先点满‘基础魔法’技能树!合理!”
第五课:包装大师——Newtype 模式
小新有 100 块钱,但他想区分“零花钱”和“压岁钱”,虽然它们都是 i32
类型。
秘籍说:“用 Newtype 模式!给类型套个‘马甲’!”
struct 零花钱(i32);
struct 压岁钱(i32);let 我的零花钱 = 零花钱(50);
let 我的压岁钱 = 压岁钱(100);// 这样就不会搞混了!
// my_allowance + my_gift_money; // 错误!类型不同,不能相加!
更厉害的是,小新可以给“零花钱”加专属技能:
impl 零花钱 {fn 买零食(&self) {if self.0 > 10 {println!("买一包巧克力饼干!");} else {println!("只能买泡泡糖了……");}}
}
小新笔记:
“太有用了!把普通类型包装一下,就有了自己的身份和技能!就像把普通石头涂上颜色,变成‘动感石头’!”
章鱼小新的“魔法符咒”课:高级类型大揭秘!
在上完“高级忍者契约”(Traits)的课程后,小新发现《Rust 忍者秘籍》还有一章叫“高级符咒(Types)”。他好奇地翻开,发现里面全是些奇奇怪怪的符号和咒语。
“这些符咒有什么用呢?”小新挠着头问自己。
第一课:马甲与外号——Newtype 模式 vs 类型别名
秘籍上画了两个相似但不同的瓶子:
- 瓶子A:贴着标签的药水瓶(Newtype 模式)
- 瓶子B:写着便签的普通杯子(类型别名)
1. 贴标签的药水瓶(Newtype 模式)
“当你需要一个全新的、独立的‘身份’时,就用这个瓶子。它虽然装的是普通水,但贴上‘隐身药水’的标签后,就成了独一无二的宝贝!”
小新明白了:
struct 隐身药水(i32); // 一个元组结构体,给 i32 套了个“马甲”
struct 解药(i32);let 我的药水 = 隐身药水(5);
let 我的解药 = 解药(5);// my_potion + my_antidote; // 错误!编译器说:你不能把“隐身药水”和“解药”相加!
即使它们内部都是 i32
,但因为“身份”不同,就不能混用。这能防止小新不小心把自己的“零花钱”当成“压岁钱”花掉!
2. 写着便签的杯子(类型别名 - Type Alias)
“当你只是觉得名字太长太麻烦,想取个‘外号’时,就用这个杯子。”
type 零花钱 = i32; // 给 i32 取个外号叫“零花钱”let x: i32 = 5;
let y: 零花钱 = 5;println!("x + y = {}", x + y); // 完全没问题!因为“零花钱”就是“i32”的外号
小新笔记:
“Newtype 是‘变身’,类型别名是‘起外号’。变身后的我(小新)和蜡笔小新是两个人;但‘小新’和‘野原新之助’说的是同一个人!”
第二课:黑洞符咒——Never 类型 (!
)
秘籍上画了一个漆黑的洞,旁边写着:!
。
“这是‘黑洞符咒’,代表‘此处无物,程序已终结’。任何东西掉进去都会消失,包括函数的返回值。”
小新看到一个例子:
fn 发动终极技() -> ! { // 返回类型是 `!`panic!("动感光波……失败了!世界毁灭!");
}
这个函数永远不会正常返回(因为它直接让程序崩溃了),所以它的“返回类型”是一个“不存在的类型”。
为什么有用?
想象小新在玩“猜数字游戏”,如果输入错误,他就“跳过这一轮”:
let guess = match input.trim().parse() {Ok(num) => num,Err(_) => continue, // continue 的“值”就是 `!`
};
continue
不会返回一个数字,它直接跳回循环开头。所以 Rust 知道,guess
的类型只能是 num
的类型(比如 u32
),完全没问题!
小新笔记:
“
!
就像游戏里的‘Game Over’画面。一旦出现,游戏结束,没有‘下一关’可言。”
第三课:幽灵与盒子——动态大小类型 (DSTs)
秘籍最后一页画着一个“幽灵”和一个“封印盒”。
“有些东西,大小不固定,就像幽灵一样无法捉摸。你不能直接拿着幽灵,必须把它关进‘盒子’里!”
小新想了想,“一段话”就是字符串。单独的 str
类型大小未知(因为每句话长短不同),所以不能这样写:
let s: str = "Hello!"; // 错误!编译器不知道该分配多大空间
正确做法是把它放进“盒子”:
let s: &str = "Hello!"; // &str 是“引用”,包含地址和长度,大小固定!
let s: Box<str> = "Hello!".into(); // 或者用智能指针 Box
同样,trait
也是一种“能力”,大小未知,也必须放进“盒子”:
let t: &dyn MyTrait = &some_object; // &dyn Trait
let t: Box<dyn MyTrait> = Box::new(some_object); // Box<dyn Trait>
小新笔记:
“原来如此!幽灵(DST)必须被‘引用’(&) 或 ‘盒子’(Box) 封印才能使用。否则它们会满屋子乱飘,把程序搞乱!”
章鱼小新的“忍术卷轴”课:函数指针与返回闭包大揭秘!
小新已经学会了写基础的忍术(函数)和现场画符的临时忍术(闭包)。但今天,秘籍翻开了一章更酷的内容:“如何把忍术当成物品传递?如何制造一个能不断生成新忍术的‘忍术工厂’?”
第一课:忍术的名字 vs. 忍术本身——函数指针 (fn
) 与函数项 (Function Item)
小新一直以为“忍术”就是写在卷轴上的咒语。但秘籍说:
“每个忍术都有两个身份:一个是它的‘名字’,另一个是它‘实际的动作’。”
想象一下:
动感光波()
是一个具体的忍术动作。- 而
动感光波
(不带括号)是这个忍术的“名字”或“地址”。
就像你可以把“动感超人”的海报贴在墙上,也可以让动感超人本人(动作)来你家。
1. 函数指针 (fn
) —— 指向忍术的箭头
小新可以创建一个“指向忍术的箭头”(函数指针),然后让别人按箭头执行。
// 定义两个忍术
fn 动感光波() {println!("动感光波!!!");
}fn 臀部炸弹() {println!("扭啊扭……轰!!!");
}// 小新制作了一个“忍术选择器”,里面装的是“指向忍术的箭头”
let mut 当前忍术: fn() = 动感光波; // fn() 是函数指针类型当前忍术(); // 执行:动感光波!!!当前忍术 = 臀部炸弹; // 箭头现在指向“臀部炸弹”
当前忍术(); // 执行:扭啊扭……轰!!!
2. 函数项 (Function Item) —— 忍术的“快照”
而像 let f = 动感光波; f();
这样的代码,用的是“函数项”,它更像是忍术的一个“快照”。通常情况下,Rust 会自动帮你处理函数项和函数指针的转换,所以小新平时没觉得有啥区别。
小新笔记:
“函数指针就像遥控器上的按钮,按下去就执行对应的忍术。我可以随时换按钮(换忍术)!”
第二课:忍术工厂——返回闭包
这是最神奇的一课!小新要学习如何制造一个“忍术生成机”(返回闭包的函数)。
问题是:闭包很特别,每个闭包都是独一无二的“定制符咒”,它们的类型连编译器在编译前都看不清长什么样。
所以,你不能这样写:
fn 制造笑声机器() -> Fn(String) -> String { // 错误!编译器不知道具体是哪个闭包类型|s| format!("{} 哈哈哈!", s)
}
那怎么办?秘籍教了两招:
方法一:装进“封印盒”——使用 Box<dyn Trait>
把生成的闭包装进一个“智能盒子”(Box
)里,并告诉盒子:“这里面是个会笑的符咒!”(dyn Fn
)。
fn 制造笑声机器() -> Box<dyn Fn(String) -> String> {Box::new(|s| format!("{} 哈哈哈!", s)) // 把闭包装进 Box 返回
}let 笑声机器 = 制造笑声机器();
println!("{}", 笑声机器("小新")); // 输出:小新 哈哈哈!
方法二:模板忍术——使用泛型和 impl Trait
如果小新知道这个“忍术生成机”生产的都是同一类忍术,他可以用“模板”来描述。
// T 是一个未知的类型,但它必须是个“会笑的符咒”
fn 制造笑声机器<T>(笑的方式: T) -> impl Fn(String) -> String
where T: Fn(&str) -> String,
{move |s| format!("{} {}", s, 笑的方式(&s))
}// 使用
let 幽默机器 = 制造笑声机器(|_| "嘿嘿嘿".to_string());
println!("{}", 幽默机器("小新")); // 输出:小新 嘿嘿嘿
这里 impl Fn(String) -> String
的意思是:“我返回一个东西,它实现了 Fn
这个能力,具体是啥类型?别管,反正你能像调用函数一样用它就行!”
小新笔记:
“返回闭包就像开一家‘表情包生成店’。每个客人(调用者)拿到的表情包(闭包)可能不同,但我保证它一定是张‘搞笑图片’(实现了 Fn trait)。用
Box
就像把表情包发电子版(在堆上分配),用impl Trait
就像给客人一张‘兑换券’,现场生成。”
小新的终极发明
学完这两课,小新兴奋地造出了“超级忍术遥控器”!
// 1. 用函数指针做菜单
type 忍术 = fn();let 忍术菜单 = vec![动感光波, 臀部炸弹];// 2. 用“忍术工厂”生成个性化攻击
fn 制造专属攻击(名字: String) -> Box<dyn Fn()> {Box::new(move || {println!("{}发动了秘密武器!", 名字);})
}let 小新攻击 = 制造专属攻击("小新".to_string());// 遥控器启动!
let mut 遥控器 = &忍术菜单[0]; // 指向“动感光波”
遥控器(); // 动感光波!!!遥控器 = &忍术菜单[1]; // 指向“臀部炸弹”
遥控器(); // 臀部炸弹!小新攻击(); // 小新发动了秘密武器!
“哇!我的遥控器既能切换预设忍术,又能播放自定义技能!爸爸再也不用担心我的战斗力了!”小新得意地跳起了屁股舞。
总结:
函数指针 (fn
):可以把函数当作值来存储和传递,实现灵活的控制。
返回闭包:由于闭包类型独特,必须用 Box<dyn Fn>
(装盒)或 impl Fn
(接口抽象)来返回,从而创建出能动态生成行为的“工厂函数”。
掌握了这些,“函数式编程”的大门就为你打开了!你的代码将变得更加灵活和强大,就像小新的超级遥控器一样!