AI Library · 知识库

# 引用与共享修改:别名与可变性陷阱

学习目标:理解“别名(Aliasing)”与“可变性(Mutability)”交织带来的隐式状态污染,掌握不同语言在多方持有数据时的防护策略。

前置知识与范围:结构体与引用指针。演示两个业务模块持有同一份配置时的并发与篡改风险。

# 问题与边界

模块 A(服务网关)与模块 B(监控组件)共享同一份运行配置 AppConfig { timeout_sec: 30 }:

  1. 别名风险场景:若两个模块持有同一个可变对象引用,模块 A 擅自执行 config.timeout_sec = 10,模块 B 会在完全不知情的情况下读取到修改后的 10,导致超时策略被破坏。
  2. 工程防御契约:
    • 方式一(编译期拦截):在语言层面禁止“多处持有且任意修改”(如 Rust 的借用规则)。
    • 方式二(不可变对象):配置对象一旦发布即为完全冻结/只读,修改必须显式创建新实例(Copy-on-Write)。

# 关键差异

语言 别名可变性防护机制 运行时 vs 编译期 推荐工程模式
Rust 共享不可变,可变不共享(Aliasing XOR Mutability) 编译期严格检查 原生 &Config 确保共享期间绝无任何写入
Go 结构体指针共享会导致隐式修改 无语言级不可变类型 值拷贝传递配置,或仅暴露只读查询方法
Java 普通对象字段可随意修改 运行时引用传递 使用不可变 record 代替传统 Setter JavaBean
JavaScript 普通对象属性自由修改 运行时动态 Object.freeze(config) 浅冻结对象
TypeScript readonly 修饰符 编译期静态检查 Readonly<Config> 强类型防篡改
Kotlin 属性声明为 val 编译期静态检查 采用不可变 data class,修改使用 copy()
Python 普通类属性完全公开 约定或第三方库 @dataclass(frozen=True) 冻结修改
C++ const 引用限制修改 编译期静态检查 传递 const Config& 禁止调用非常规成员

# 各语言实现

# Rust

Ruststruct AppConfig {
    timeout_sec: u32,
}

struct Gateway<'a> {
    config: &'a AppConfig,
}

struct Monitor<'a> {
    config: &'a AppConfig,
}

fn main() {
    let config = AppConfig { timeout_sec: 30 };

    // Gateway 和 Monitor 均持有不可变共享引用 (&AppConfig)
    let gw = Gateway { config: &config };
    let mon = Monitor { config: &config };

    assert_eq!(gw.config.timeout_sec, 30);
    assert_eq!(mon.config.timeout_sec, 30);

    // 在借用存在期间,尝试将 config 改为可变并写入会被 rustc 编译直接拒绝!
    println!("timeout: {}", mon.config.timeout_sec);
}

Rust 著名的借用检查器规则(Borrow Checker):只要存在任何活跃的不可变引用 &T,任何写入或获取可变引用 &mut T 的代码都无法通过编译。

# Go

Gopackage main

import "fmt"

type AppConfig struct {
	TimeoutSec int
}

// 采用值传递(每个组件获得独立的配置快照副本)
type Gateway struct {
	config AppConfig
}

type Monitor struct {
	config AppConfig
}

func main() {
	original := AppConfig{TimeoutSec: 30}
	gw := Gateway{config: original}
	mon := Monitor{config: original}

	// 即使 gw 内部修改自身配置,mon 的状态严格隔离
	gw.config.TimeoutSec = 10

	if gw.config.TimeoutSec != 10 || mon.config.TimeoutSec != 30 {
		panic("isolation failed")
	}

	fmt.Printf("mon_timeout: %d\n", mon.config.TimeoutSec)
}

Go 中防御隐式篡改的最有效手段就是值拷贝(Pass-by-value)。结构体值复制阻断了指针别名的连锁反应。

# Java

Javapublic class Main {
    // 使用不可变 record
    public record AppConfig(int timeoutSec) {}

    public static class Gateway {
        public final AppConfig config;
        public Gateway(AppConfig config) { this.config = config; }
    }

    public static class Monitor {
        public final AppConfig config;
        public Monitor(AppConfig config) { this.config = config; }
    }

    public static void main(String[] args) {
        AppConfig config = new AppConfig(30);
        Gateway gw = new Gateway(config);
        Monitor mon = new Monitor(config);

        assert gw.config.timeoutSec() == 30;
        assert mon.config.timeoutSec() == 30;

        // config 没有 setter,无法被任何一方篡改
        System.out.println("mon_timeout: " + mon.config.timeoutSec());
    }
}

Java 16+ 的 record 是纯不可变的,字段均为 final,杜绝了多模块共享可变状态的灾难。

# JavaScript

JavaScriptconst assert = {
  equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn, errType) => {
    try { fn(); throw new Error("expected throw"); } catch (e) {
      if (e.message === "expected throw") throw e;
      if (errType && !(e instanceof errType)) throw e;
    }
  }
};

const config = Object.freeze({
  timeoutSec: 30,
});

const gw = { config };
const mon = { config };

assert.equal(gw.config.timeoutSec, 30);
assert.equal(mon.config.timeoutSec, 30);

// 在严格模式下,修改被 freeze 的对象会直接抛出 TypeError
assert.throws(() => {
  "use strict";
  gw.config.timeoutSec = 10;
}, TypeError);

assert.equal(mon.config.timeoutSec, 30);

console.log(`mon_timeout: ${mon.config.timeoutSec}`);

JavaScript 的 Object.freeze 在运行时冻结对象,阻止任何属性的修改、添加或删除。

# TypeScript

TypeScriptconst assert = {
  equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn: () => void, errType?: unknown) => {
    try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
      if (e instanceof Error && e.message === "expected throw") throw e;
      if (typeof errType === "function" && !(e instanceof errType)) throw e;
    }
  }
};

interface AppConfig {
  readonly timeoutSec: number;
}

const config: AppConfig = { timeoutSec: 30 };

class Gateway {
  constructor(readonly config: AppConfig) {}
}

class Monitor {
  constructor(readonly config: AppConfig) {}
}

const gw = new Gateway(config);
const mon = new Monitor(config);

assert.equal(gw.config.timeoutSec, 30);
assert.equal(mon.config.timeoutSec, 30);

console.log(`mon_timeout: ${mon.config.timeoutSec}`);

TypeScript 的 readonly 修饰符让属性不可赋值,若调用方写 gw.config.timeoutSec = 10,编译器直接报错。

# Kotlin

Kotlindata class AppConfig(val timeoutSec: Int)

class Gateway(val config: AppConfig)
class Monitor(val config: AppConfig)

fun main() {
    val config = AppConfig(timeoutSec = 30)
    val gw = Gateway(config)
    val mon = Monitor(config)

    check(gw.config.timeoutSec == 30)
    check(mon.config.timeoutSec == 30)

    // val 字段无法被重新赋值
    println("mon_timeout: ${mon.config.timeoutSec}")
}

Kotlin 的不可变属性 val 构成了不可变数据模型的基础。

# Python

Pythonfrom dataclasses import dataclass, FrozenInstanceError

@dataclass(frozen=True)
class AppConfig:
    timeout_sec: int

class Gateway:
    def __init__(self, config: AppConfig):
        self.config = config

class Monitor:
    def __init__(self, config: AppConfig):
        self.config = config

if __name__ == "__main__":
    config = AppConfig(timeout_sec=30)
    gw = Gateway(config)
    mon = Monitor(config)

    assert gw.config.timeout_sec == 30
    assert mon.config.timeout_sec == 30

    try:
        gw.config.timeout_sec = 10
        assert False, "should fail on frozen dataclass"
    except FrozenInstanceError:
        pass

    assert mon.config.timeout_sec == 30

    print(f"mon_timeout: {mon.config.timeout_sec}")

Python 的 @dataclass(frozen=True) 在写入属性时自动拦截并抛出 FrozenInstanceError。

# C++

C++#include <iostream>
#include <cassert>

struct AppConfig {
    int timeout_sec;
};

class Gateway {
public:
    const AppConfig& config;
    explicit Gateway(const AppConfig& cfg) : config(cfg) {}
};

class Monitor {
public:
    const AppConfig& config;
    explicit Monitor(const AppConfig& cfg) : config(cfg) {}
};

int main() {
    AppConfig config{30};
    Gateway gw(config);
    Monitor mon(config);

    assert(gw.config.timeout_sec == 30);
    assert(mon.config.timeout_sec == 30);

    // const 引用只读,试图修改成员在编译期被拦截
    std::cout << "mon_timeout: " << mon.config.timeout_sec << "\n";
}

C++ 通过 const& 标明只读视角,任何试图通过该引用修改对象的操作都会在编译期报错。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本mon_timeout: 30

别名不可变性与篡改防护测试全部通过。

# 相关阅读