惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
Stack Overflow Blog
Stack Overflow Blog
B
Blog
I
InfoQ
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
F
Full Disclosure
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
T
Tailwind CSS Blog
S
Secure Thoughts
P
Privacy International News Feed
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 最新话题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
Cybersecurity and Infrastructure Security Agency CISA
Last Week in AI
Last Week in AI
W
WeLiveSecurity
Google Online Security Blog
Google Online Security Blog
P
Privacy & Cybersecurity Law Blog
D
DataBreaches.Net
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
P
Palo Alto Networks Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Project Zero
Project Zero
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
博客园 - Franky
阮一峰的网络日志
阮一峰的网络日志
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
H
Help Net Security
Martin Fowler
Martin Fowler
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
G
GRAHAM CLULEY
博客园 - 【当耐特】

smallyu的博客

为什么买的加密货币一定要去中心化 RISC-V 虚拟机无法代替 EVM 虚拟机 Polkadot 的 Existential Deposit 机制错在哪里 利息,套利,交易策略,金融市场 LayerZero 从来不挑战比特币的地位 默认参数引起的以太坊节点运行错误 硬件钱包与资产安全 预测市场比币圈更赌场 为什么去中心化的跨链桥不可能实现 我对于 AI 时代的答案 不要投资任何隐私币 区块链技术世界的三大真理 以太坊 AA 钱包的致命问题是什么 基于 AI 语义执行的 MCP 区块链的设计 一个集成 Geth 和 CometBFT 的兼容层 我的加密货币定投策略(二) DeFi 基础: 理解 AMM 定价机制 Go 语言 GMP 调度器的原理是什么 Web3 项目分析计划 对 Psyche Network 项目的分析 continuation 教程: 理解 CPS 如何开发一个比特币符文(Runes)协议 比特币脚本开发教程 我从王垠的计算机科学视频班学到了什么 区块链技术面试题(2025年版) Solana 智能合约开发教程 (1) 尝试开发一个最小 EVM 虚拟机 基于 ZK 的链上身份系统设计 一个 Web3 打赏系统的设计 鼓吹 Cursor 的人技术能力都差 关于 Code Review 的礼节 假如启动一个新的以太坊 PoS 网络 发行加密货币的最好方式 所有 BFT 共识的区块链都是中心化的 对 2025 年区块链行业的预测 Restaking 项目的经济难题 如何看懂任意区块链项目的技术架构 为什么不要做以太坊的二次开发 为什么不要做智能合约开发和 DeFi 开发 我的加密货币定投策略(一) PoS 类型的区块链如何处理分叉 Ethereum Casper 为什么需要 EIP-7251 区块链中的 PBFT 不需要第二次投票 开发者的思维方式 发币的核心要点 炒币投资的小 tips 为什么说 PoW 比 PoS 更加去中心化 牛市对普通人来说意味着什么 如何健康地远程工作 为什么比特币不用概率加密函数 程序员的 “服从权威” 心理 区块链技术面试题 如何区分公有链和联盟链 对 Layer 2 项目创业想法的回复 对区块链共识机制的理解 Pebbling Game 鹅卵石游戏 PDP 文件证明的局限性 不要小瞧 ChatGPT 为什么炒币不是一个好主意 一种在区块链上生成随机数的机制 为什么以太坊的私钥计算不可逆 关于以太坊的私钥碰撞 “猜均值的2/3” dApp 游戏设计 Proof of Storage/Space/Replication 的区别 Proofs of Retrievability 文件证明的含义 对 S-PDP 文件证明的示例和解释 我的加密货币交易机器人 对区块链行业的见闻 随机确认块的共识机制 VRF + BFT 共识引起交易失败的问题 为什么要重视编程思想 对 Web 3.0 的理解 GitBook 好用吗? 一种区块链节点存储扩容的方式 一种基于“自我中心主义”的共识机制 链表常见算法题及解析 基于 Multi-Linked List 的区块链设想 理解哈希函数与序列化 联盟链比公有链差在哪儿 在 Dijkstra 算法中保存路径 为什么数字货币使用区块链是政治问题 区块链:下一代数字身份认证体系的基石 网页技术能实现 3D 建模吗? 给区块链一个定义 从 Erlang 开始了解 Actor 模型 一种侧边导航栏的交互方式 Rust 的 ownership 是什么? Haskell 中的 Monad 是什么? 浅析 Libra 背后的区块链技术 对区块链的理性认识 Rust 基础语法概述 基于 Java 的爬虫框架 WebCollector Kotlin:简化版的 Scala JavaScript 有关联数组吗? 主流编程语言的异常处理机制 Go 语言基本语法 Scala 语法基础 用 Scala 改写 Java 浅度实践 Python 获取海贼王更新信息 HTML5 音乐可视化
Rust 语言容易让新手困惑的一个“过度优化”
2025-06-29 · via smallyu的博客

2025-06-30

假如我们现在要写一些代码,随便用 cargo new 一个项目就行,然后写一个函数 append,函数的功能很好理解,就是把两个传入的字符串给拼接起来,第一个参数是字符串(的引用类型),第二个参数也是字符串,假如我们的参数是 Hello, world,函数调用后会返回 Hello, world 给我们。函数具体这样写:

fn append(s1: &String, s2: &String) -> String {
    return s1.clone() + s2.clone().as_str();
}

不需要关心 return 后面的语句写法,这不是我们关注的重点。在入口函数 main 里调用这个 append,运行一下,输出的内容会和我们预期一样,打印出拼接后的字符串 Hello, world

fn main() {
    let s1: String = String::from("Hello");
    let s2: String = String::from(", world");
    println!("{}", append(&s1, &s2));
}

那么现在,保持 append 函数完全不变,在 main 函数里修改两个字符串的定义,整个 main 函数变成这样,猜一下输出结果会是什么?注意 Rust 是静态类型的语言,编译器对于变量类型往往具有严格的定义和判断:

fn main() {
    let s1: Box<String> = Box::new(String::from("Hello"));
    let s2: Box<String> = Box::new(String::from(", world"));
    println!("{}", append(&s1, &s2));
}

我们首先的直觉是应该编译报错,因为 s1 的类型是 Box<String>,调用 append 函数的时候,传入的参数为 &s1,对应的类型为 &Box<String>,而显然 append 函数的定义是没有修改的,接收的参数类型仍然是 &String。那么这种情况下,为什么编译器没有报错,而且代码还能正常运行,输出了 Hello, world 的结果?(先别管这里的 Box 是什么,反正是一种类型)

我们接着再修改一下 main 函数的内容,把字符串的定义改为这样:

fn main() {
    use std::rc::Rc;

    let s1: Rc<String> = Rc::new(String::from("Hello"));
    let s2: Rc<String> = Rc::new(String::from(", world"));
    println!("{}", append(&s1, &s2));
}

代码能通过编译吗?能正常运行吗?append 函数的定义仍然没有变,这里 main 函数中 s1 的类型变成了 Rc<String>,相应的传入 append 函数做参数的时候,类型变为了 &Rc<String>。但是为什么,编译器没有报错,而且还能正常运行出结果,输出 Hello, world?(同样别管 Rc 是什么,也是一种类型)

根据刚才的代码片段,我们观察到一个现象:当函数的参数类型是 &String 的时候,既可以接受 &String 类型的参数,也可以接收 &Box<String> 类型的参数,还可以接收 &Rc<String> 类型的参数。

再疯狂一点,如果把 main 函数改成这样呢?

fn main() {
    let s1: Box<Box<Box<Box<String>>>> = Box::new(Box::new(Box::new(Box::new(String::from("Hello")))));
    let s2: Box<Box<Box<Box<String>>>> = Box::new(Box::new(Box::new(Box::new(String::from(", world")))));
    println!("{}", append(&s1, &s2));
}

如果把 main 函数改成这样呢?

fn main() {
    use std::rc::Rc;
    
    let s1: Rc<Rc<Rc<Rc<String>>>> = Rc::new(Rc::new(Rc::new(Rc::new(String::from("hello")))));
    let s2: Rc<Rc<Rc<Rc<String>>>> = Rc::new(Rc::new(Rc::new(Rc::new(String::from(", world")))));
    println!("{}", append(&s1, &s2));
}

结果是 main 函数都可以正常运行,输出 Hello, world 的结果。

为了进一步观察关于类型的问题,现在新写两个 append 函数,append2 函数接收的类型是 &Box<String>,而 append3 函数接收的类型是 &Rc<String>

fn append2(s1: &Box<String>, s2: &Box<String>) -> Box<String> {
    let mut result = (**s1).clone();
    result.push_str(s2);
    Box::new(result)
}

use std::rc::Rc;
fn append3(s1: &Rc<String>, s2: &Rc<String>) -> Rc<String> {
    let mut result = (**s1).clone();
    result.push_str(s2);
    Rc::new(result)
}

接下来分析一下,对于下面的 main 函数代码,编译器会在哪一行报错?

fn main() {
    let s1: Box<Box<Rc<Rc<String>>>> = Box::new(Box::new(Rc::new(Rc::new(String::from("hello")))));
    let s2: Box<Box<Rc<Rc<String>>>> = Box::new(Box::new(Rc::new(Rc::new(String::from(", world")))));
    println!("{}", append(&s1, &s2));
    println!("{}", append2(&s1, &s2));
    println!("{}", append3(&s1, &s2));
}

这样呢,字符串的类型再扩展一下,编译器还会报错吗,在哪一行?

fn main() {
    let s1: Box<Box<Rc<Rc<Box<Box<String>>>>>> = Box::new(Box::new(Rc::new(Rc::new(Box::new(Box::new(String::from("hello")))))));
    let s2: Box<Box<Rc<Rc<Box<Box<String>>>>>> = Box::new(Box::new(Rc::new(Rc::new(Box::new(Box::new(String::from(", world")))))));
    println!("{}", append(&s1, &s2));
    println!("{}", append2(&s1, &s2));
    println!("{}", append3(&s1, &s2));
}

Rust 把这种语言特性叫做人体工学设计,为了减轻开发人员的负担。但是 Rust 在设计动不动会把变量给 move 掉、不得不使用 ' 单引号写法的时候,却放弃了人体工学,把内存安全放在了更重要的地位……倒是也没什么错,毕竟 Rust 只有内存安全是绝不能放松的。

最后再来个进阶难度的,假如在实际的业务场景中,有一个叫 do_something 的函数,接收泛型类型的参数,我们需要对这个函数基于原有逻辑做一些改动,原本的函数逻辑是这样:

fn do_something<T1, T2>(t1: T1, t2: T2) {
    println!("{}", append(&t1, &t2));
}

现在新增加一些处理:

fn do_something<T1, T2>(t1: T1, t2: T2) {
    // 增加一个函数来处理 t1
    handle_t1(&t1);  

    println!("{}", append(&t1, &t2));
}

那么问题来了,参数 t1 的类型是什么?handle_t1 函数的参数类型应该如何定义?在原有逻辑中,t1 作为参数对 append 函数进行了调用,是否意味着 t1 的类型是 &String?如果不是 &Stringt1 的类型可能是什么?