# 值语义与共享语义

> 

本文档定义 Dujie 在语言层面对“赋值、传参、返回、修改、共享”的基本语义。

本文是类型系统总纲的专题文档，服务于 UI DSL 的当前语义设计，并为未来可能出现的响应式能力保留边界。它约束的是用户可见语义，而不是 Rust 后端的具体实现方式。

## 问题背景

当前文档中，曾经使用“值类型 / 引用类型”来解释语言行为，并把 `string`、`list<T>`、`map<K, V>`、`opt<T>`、`iter<T>`、`widget`、`struct` 等都归到“引用类型”里。

这种做法有几个问题：

<steps level="4">

#### 它把用户可见语义和 Rust 后端实现强绑定

#### 它会制造隐藏别名，降低局部可推理性

#### 它不利于 UI DSL 的状态建模

#### 它会让未来响应式系统和普通数据模型混在一起

</steps>

对于 Dujie 这样的 UI DSL，普通数据和值的行为应尽量稳定、直接、可局部推导。共享、订阅、依赖传播等能力如果未来需要引入，也应由独立机制显式承担，而不是通过“所有容器类型和结构体默认共享可变状态”间接得到。

## 设计目标

<steps level="4">

#### 让赋值、传参、返回值语义对用户可预测

#### 让普通数据建模不依赖隐藏别名

#### 为未来可能的响应式能力保留边界，但不通过普通容器类型偷带共享语义

#### 保留后端使用结构共享、写时复制、引用计数等优化的空间

</steps>

## 核心结论

### 1. Dujie 不对用户暴露通用“引用类型”分类

语言层不再把普通类型分为“值类型”和“引用类型”两大公开类别，并据此要求用户理解别名传播。

### 2. 普通 Dujie 值默认采用值语义

除非未来某类类型被明确指定为特殊引用型或响应式类型，否则普通 Dujie 值在以下场景都采用值语义：

- 变量赋值
- 函数传参
- 函数返回
- 容器和结构体字段传播

也就是说，这些操作得到的是“逻辑上独立的值”。后续对其中一个值的修改，不应影响另一个值。

### 3. 存储共享可以作为实现优化，但不能成为用户可观察语义

后端可以使用以下技术降低拷贝成本：

- 结构共享
- 写时复制（copy-on-write）
- 引用计数
- 持久化数据结构

但这些都属于实现策略，不应改变用户在语言层观察到的结果。

### 4. 未来若引入响应式共享，也不由普通类型承担

如果未来 Dujie 需要引入响应式系统，那么：

- 依赖收集
- 更新传播
- 显式共享状态
- 订阅关系

都应通过独立的响应式原语表达，而不是让 `list`、`map`、`struct` 默认共享底层可变状态。

## 术语

### 值语义

值语义指：

- 赋值、传参、返回时得到逻辑上独立的值
- 之后对其中一个值的修改，不影响另一个值

### 共享实现

共享实现指：

- 编译器或运行时内部可以复用底层存储
- 但用户不能通过普通语言操作观察到“隐藏别名”

### 可变绑定

`var` 表示该绑定可被更新。

这里的“更新”包括：

- 重新赋值
- 通过该绑定修改其当前值的可变部分，例如 list/map/struct

`let` 表示该绑定不可更新。

## 容器类型与结构体

本文中的“容器类型”专指：

- `list<T>`
- `map<K, V>`
- `opt<T>`
- `iter<T>`

`struct` 不归入容器类型。它是名义类型，但在值语义讨论中同样属于“可包含其他值的聚合数据”。

## 按类型分类的语义规则

### 1. 基础类型

`int`、`float`、`bool`、`rune`、`string` 都按普通值处理。

其中：

- `string` 是不可变值
- `rune` 表示单个 Unicode 码点
- 基础类型之间不存在“共享修改”的语言语义

`string` 不再被视为用户层面的“引用类型”。

### 2. 容器类型与结构体

`list<T>`、`map<K, V>`、`struct` 在语言层都视为普通数据值。

它们可以有更新操作，但这些更新只作用于当前值本身，不通过赋值关系隐式传播到其他变量。

#### 例子：list

```dj
var a = [1, 2, 3];
var b = a;

b[0] = 10;

// a == [1, 2, 3]
// b == [10, 2, 3]
```

#### 例子：map

```dj
var a = {"x": 1};
var b = a;

b["x"] = 2;

// a == {"x": 1}
// b == {"x": 2}
```

#### 例子：struct

```dj
var a = Pair { key: "x", value: [1, 2, 3] };
var b = a;

b.value[0] = 10;

// a.value == [1, 2, 3]
// b.value == [10, 2, 3]
```

### 3. `opt<T>`

`opt<T>` 是值包装类型。

它只表达“有值 / 无值”，不额外引入共享语义。

其值语义由 `T` 的普通值语义继承而来。

### 4. `widget`

`widget` 表示 UI 组件值。

它在语言层应视为不可变值，而不是可共享可变对象。

原因：

- UI 组件树更适合值式描述
- 这更利于后续做 diff、重建和响应式更新
- 这避免把“组件节点句柄”暴露成可变引用对象

### 5. `iter<T>`

`iter<T>` 不应再被归入“引用类型”讨论。

它更接近一种瞬时遍历对象，而不是持久数据容器。

当前只先约定：

- `iter<T>` 不承载普通共享修改语义
- 它不是用来表达共享可变状态的类型

关于“是否可重复消费、是否单次遍历、赋值后如何观察其状态”等问题，后续另开文档处理。

### 6. `any`

`any` 不改变值语义与共享语义的基本判断。

它只擦除静态类型信息，不自动把普通值变成共享引用对象。

## 函数传参与返回值

普通函数参数和返回值也采用值语义。

### 例子：函数内部修改不应外溢

```dj
func append_one(xs: list<int>) -> list<int> {
    var out = xs;
    out.push(1);
    return out;
}

var a = [1, 2];
var b = append_one(a);

// a == [1, 2]
// b == [1, 2, 1]
```

这意味着：

- 普通参数不是隐式引用参数
- 如果想表达“调用后外部状态被更新”，未来应使用显式机制，而不是依赖别名传播

## 对未来响应式系统的约束

本设计对未来响应式系统有一个明确要求：

普通数据值和响应式状态必须区分。

也就是说，未来响应式系统不应通过下面这种方式构建：

- 把 list/map/struct 默认做成共享可变引用
- 再依赖别名变化触发更新

更合理的方向是：

- 普通值保持值语义
- 响应式能力由专门的状态类型或响应式原语承担

这样可以避免“我只是传了一个值，为什么另一个地方的 UI 也被隐式改掉”这类问题。

## 允许的实现策略

本设计允许编译器或运行时采用多种内部策略实现值语义，包括：

- 小对象直接拷贝
- 聚合对象的结构共享
- 写时复制
- 引用计数包装
- 针对 `widget` 的持久化树结构

但这些策略只能影响性能，不能改变语言层可见行为。

## 明确拒绝的设计

当前明确拒绝以下语言级设计：

### 1. 普通聚合类型默认共享可变状态

也就是拒绝把 `list`、`map`、`struct` 的普通赋值定义为“只复制引用”。

### 2. 用 Rust 后端表示直接反推语言语义

例如：

- 因为后端用了 `Arc<T>`，所以语言里就认为它是引用类型
- 因为后端用了 `Vec<T>`，所以语言里就必须暴露某种 Rust 风格移动规则

这些都不应成立。

### 3. 用普通值模型代替未来响应式模型

响应式系统需要显式设计，不应伪装成普通值赋值语义的一部分。

## 对现有文档的影响

### `00.guide/03.data-types`

后续应重写其中“值类型 / 引用类型”的用户说明，不再把大量类型直接定义为共享引用。

### `01.design/06.struct-design`

该文档目前更适合被视为“某种后端实现探索”，而不是结构体的最终语言语义定义。

### `widget` 与未来响应式设计

后续讨论 `widget` 和响应式系统时，应默认以“组件值”而不是“组件引用对象”为前提。

## 未决问题

以下问题后续继续讨论：

<steps level="4">

#### `iter<T>` 的消费语义

#### list/map/struct 的更新操作是否都要求 `var`

#### 结构体字段更新的完整规则

#### 未来响应式原语的名称和基本模型

</steps>
