从 `a = b` 说起:你真的理解“赋值”吗?

37 阅读3分钟

在绝大多数编程语言里,a = b 是我们写下的第一个语句之一。

它看起来如此简单,以至于我们几乎从不怀疑它的含义。但如果你稍微停下来想一想,就会发现一个有趣的事实:

a = b 在不同语境下,可能表达的是完全不同的事情。

甚至可以说,围绕 a = b,隐藏着编程语言中最核心的一些概念:值、引用、拷贝、所有权、生命周期

这篇文章就从 a = b 这个最普通的表达式出发,试着把这些问题一次讲清楚。


一、a = b 的三种直觉理解

如果暂时不限定具体语言,仅凭直觉来看,a = b 至少可能有三种含义。

1️⃣ 引用赋值:ab 指向同一个东西

b 的引用赋值给 a

let b = { x: 1 }
let a = b

a.x = 2
console.log(b.x) // 2

此时:

  • 并没有“复制”任何数据
  • ab 只是指向同一块内存
  • 修改其中一个,另一个也会受影响

这种语义可以总结为:

赋值的是“地址 / 引用”,而不是值本身

这是 JavaScript、Java、Python 等语言中对象赋值的默认行为


2️⃣ 值拷贝:a 拿到的是 b 的一个副本

int b = 10;
int a = b;

a = 20;
// b 仍然是 10

这里的 a = b 表达的是:

  • 复制一份值
  • ab 彼此独立
  • 后续修改互不影响

这种语义强调的是:

内容相同,但身份不同

也就是我们通常说的值语义(Value Semantics)


3️⃣ 所有权转移:b 交出资源,之后不再可用

let b = String::from("hello");
let a = b;

// println!("{}", b); // 编译错误

在 Rust 中,a = b 的含义是:

  • 资源的所有权b 转移到 a
  • b 在这之后失效
  • 保证“同一时间只有一个所有者”

这是一种移动语义(Move Semantics)


二、关键问题:为什么同一个 = 会有不同含义?

答案是:

= 本身没有语义,语义来自语言的设计选择。

编程语言在设计时,必须回答一个问题:

当我写下 a = b,我到底是在操作什么?

不同语言给出了不同答案。


三、三种赋值语义背后的设计取舍

我们可以把这三种语义抽象成三种模型。

模型关注点优点代价
引用语义身份(identity)高效、灵活易产生副作用
值语义内容(value)安全、直观拷贝成本
所有权语义资源唯一性内存安全心智负担

JavaScript 的选择

  • 性能 + 易用性优先
  • 对象默认是引用语义
  • 代价是:
    • 不可变性需要人为约束
    • 深拷贝成本高且容易踩坑

Rust 的选择

  • 安全优先
  • 强制你正视“谁拥有资源”
  • 代价是:
    • 学习曲线陡峭
    • 写代码时必须时刻思考生命周期

四、为什么这件事很重要?

因为很多 Bug,本质上都是“对 a = b 的误解”

当你写下:

const newState = oldState

你真的确定这是你想要的吗?


五、一个更抽象的视角

如果我们跳出编程语言,会发现一个有趣的类比:

现实世界中,很多冲突也源自“语义不一致”。

a = b 只是把这些问题,用最极简的形式暴露了出来。


六、总结

  • a = b 不是一个简单的语法问题,而是一个语义问题
  • 它至少可能意味着:
    1. 共享同一份资源(引用)
    2. 复制一份内容(拷贝)
    3. 转移资源所有权(移动)
  • 不同语言,只是做了不同的默认选择

真正重要的不是你写了 a = b, 而是你是否清楚,它在当前语境下“意味着什么”。