2 分•作者: xqcgrek2•6 个月前
返回首页
最新
1 分•作者: dangtony98•6 个月前
31 分•作者: sollewitt•6 个月前
73 分•作者: cdrnsf•6 个月前
20 分•作者: user_7832•6 个月前
9 分•作者: krasun•6 个月前
100 分•作者: Mojah•6 个月前
1 分•作者: cpuXguy•6 个月前
2 分•作者: gushogg-blake•6 个月前
1 分•作者: recursivegirth•6 个月前
1 分•作者: ckardaris•6 个月前
1 分•作者: throwaway2027•6 个月前
1 分•作者: ADCXLAB•6 个月前
1 分•作者: PaulHoule•6 个月前
1 分•作者: mooreds•6 个月前
3 分•作者: firasd•6 个月前
1 分•作者: mooreds•6 个月前
21 分•作者: mooreds•6 个月前
4 分•作者: stevendgarcia•6 个月前
我一直在探索一种新的系统语言设计,它围绕着一个硬性规则构建:
数据在其创建的词法作用域内存在。
外部作用域永远不能保留对内部分配的引用。
没有垃圾回收(GC)。
没有传统的 Rust 风格的借用检查器。
没有隐藏的生命周期。
没有隐式的引用计数。
当一个作用域退出时,其中分配的所有内容都会被确定性地释放。
---
以下是代码中的基本思想:
```
fn handler() {
let user = load_user() // 任务作用域的分配
CACHE.set(user) // 编译错误:从内部作用域逸出
CACHE.set(user.clone()) // 显式逸出
}
```
如果数据需要逸出作用域,则必须显式克隆或移动。
编译器在编译时强制执行这些边界。
没有运行时生命周期检查。
内存管理变成了一种结构不变性。
程序结构使误用无法表示,而不是运行时跟踪生命周期。
并发遵循相同的包含规则。
```
fn fetch_all(ids: [Id]) -> Result<[User]> {
parallel {
let users = fetch_users(ids)?
let prefs = fetch_prefs(ids)?
}
merge(users, prefs)
}
```
如果任何分支失败,则整个并行作用域将被取消,并且其中所有分配都将被确定性地释放。
这在字面意义上是结构化并发:当一个并行作用域退出(成功或失败)时,其内存会自动清理。
失败和重试也是显式的控制流,而不是异常状态:
```
let result = restart {
process_request(req)?
}
```
重启会丢弃整个作用域并从头开始重试。
没有部分状态。
没有手动清理逻辑。
---
为什么我认为这有显著的不同:
该模型围绕着包含,而不是熵。
某些不安全状态不是通过约定或纪律来防止的,而是通过结构来防止的。
这消除了:
* 隐式生命周期和隐藏的内存管理
* 内存泄漏和悬挂指针(作用域是所有者)
* 跨不相关生命周期的共享可变状态
如果数据必须比作用域存在更长时间,则必须在代码中明确说明这一事实。
---
我现在试图学习的内容:
1. 可扩展性。这是否可以用于长时间运行、高性能的服务器,而无需退回到 GC 或普遍的引用计数?
2. 效果隔离。I/O 和副作用应该如何与基于作用域的重试或取消交互?
3. 世代句柄。这是否可以在没有过度开销的情况下取代传统的借用?
4. 故障模式。与 Rust、Go 或 Erlang 相比,这种模型在哪里崩溃?
5. 可用性。哪些常见模式变得不可能,这些是有用的约束还是决定性因素?
---
一些幕后附加想法,仍在探索中:
* 具有 epoch 风格管理的结构化并发(没有全局原子操作)
* 每个核心严格固定的执行区域,具有无锁分配
* 仅崩溃重试,其中失败总是会丢弃整个作用域
---
但首先要解决的核心问题是:
像这样的严格作用域包含的内存模型实际上是否可以在实践中工作,而不会悄悄地重新引入 GC 或传统的生命周期机制?
注意:这并非旨在成为“与 Rust 不同”或对旧系统的怀旧之情。
这是一种尝试,旨在探索一种从根本上不同的方式来思考内存和并发。
我很乐意收到关于这方面可行性的关键反馈——以及它在哪里崩溃。
感谢您的阅读。
30 分•作者: birdculture•6 个月前