以 Rust 为例,聊聊线性类型,以及整个类型系统

本文作者 Rust 小白(别的语言也了解的不多),如有错误务必指正,本文目的在于以小白的视角讲述未来 Rust 类型系统的一些演变方向。类型系统的概念往往是跨语言的,通过了解不同语言的特点,能够帮助我们更好掌握当下在用的工具。


1. 对象应当如何被使用?------ Substructural Type System

首先 Substructural Type System 这个词可能可以被翻译为"子结构类型系统"或"亚结构类型系统",但不管是什么,其名字听起来都很玄学,事实上它的核心是:通过限制对象的使用方式,来增强代码的安全性,并能更好的管理内存。

提到"亚结构"肯定有人要问:那"结构类型系统"(Structural Type System)是什么呢?,事实上,它们几乎没有关系,与"结构类型系统"对应的是"标明类型系统"(Nominal Type System):

  • 标明类型系统:名字叫啥就是啥类型,比较的是类型本身。
  • 结构类型系统:即 Duck Typing,只要类型成员均一致,那么就是相同类型,本质上类型检查只检查 Shape,即类型定义的约束条件。

但 Substructural Typing System 其实是完全不同的一个概念,其关注的是"对象应该如何被使用"。(而非具体是什么结构)下面给出了其具体分类(从宽松到严格):

亚结构类型名 使用规则 内存语义 代表语言/特性
Normal(普通) 随便用几次都行 值可以随意复制、随意丢弃 js、java、py 等多数主流语言
Affine(仿射) 最多用一次 值只能移动(所有权转移),可以不用直接 drop 当前 Rust
Linear(线性) 恰好用一次 值必须被显式消费,不能 drop 也不能 leak Rust 未来的 !Forget
Ordered(有序) 恰好一次,且在原地 值必须被消费,且内存地址从创建到销毁不变 Rust 未来的 !Move

Substructural Type System 通过控制几条"结构规则"来定义不同强度的类型约束:

  • 弱化规则(Weakening):允许引入一个变量但不使用它。即:值可以被随意丢弃。
  • 收缩规则(Contraction):允许同一个变量被使用多次。在 Rust 中,只有可 Copy 的才可以这样。
  • 有序规则(Ordered):不仅恰好使用一次,还必须按照引入顺序使用。在实践中,这意味着值有稳定的内存地址,永远不会被移动。

通过组合"有/无弱化"、"有/无收缩"、"有/无序",我们也可以上述的几种类型系统。

JavaScript、Java 等这些以引用和 GC 为核心的语言,通常都是 Normal 类型:let y = x 只是复制引用,x 仍然可用,GC 负责回收,程序员完全不用操心值的生命周期。

Rust 选择了 Affine 类型系统,这是 Rust 内存安全的根基。这里我也提到了一些 Rust 未来可能的语言设计(其实是我菜,没用过其它有这些特性的语言),后文你将会看到它们如何发挥作用。

2. 为什么 Rust 需要线性类型?

2.1 Rust 的仿射类型(Affine)

Rust 目前是一个仿射类型系统(Affine):

  • 有弱化规则 :Rust 的 RAII 机制(Drop trait)会在值离开作用域时自动析构它。你也可以用 mem::forget 直接泄露它。
  • 受限的收缩规则 :只有实现了 Copy 的类型才能隐式复制,其他类型需要显式 clone()。不实现 Copy 的类型遵循移动语义。

Rust 中非 Copy 类型的值遵循移动语义,每次赋值或传参都是所有权转移,原变量立即失效:

rust 复制代码
let x = String::from("hello");
let y = x;        // x 的所有权转移给了 y
// println!("{x}"); // 编译错误!x 已经被"用掉"了
println!("{y}");   // OK

// 复制必须显式 clone
let a = String::from("hello");
let b = a.clone(); // a 仍然可用
println!("{a} {b}"); // OK

这套机制让编译器能在编译时保证:没有 use-after-free,没有 double-free,没有数据竞争

但仿射类型有一个漏洞:值可以不用就直接丢弃mem::forget / 隐式 drop),这在某些场景下会出问题。这正是线性类型要解决的,编译器要强制你必须消费这个值。

2.2 为什么"可以丢掉"是个问题?

在 Rust 中,mem::forget 是一个安全函数 (不需要 unsafe),它可以让任意值"凭空消失"而不运行析构函数:

rust 复制代码
use std::mem;

let important_handle = acquire_critical_resource();
mem::forget(important_handle); // 析构函数不会运行,资源可能泄露

对于大多数类型来说,这只是内存泄露,这不算什么大事。但对于某些类型(比如持有系统资源的 handle、需要 join 的线程 guard),析构函数不运行可能导致未定义行为

如果我们能让某些类型变成线性类型,编译器就会在编译时保证:这个值必须被消费,不能悄悄丢掉

不过,在开始 Rust 的线性类型之前,我其实很疑惑,为什么 mem::forget 的安全的?为什么 Rust 允许析构函数不被调用?

2.3 mem::forget 为什么是安全的?

就如同上面的例子,这个行为看起来很危险,为什么它不是 unsafe 的?

设计原因 :事实上,Rust 的核心立场是 内存泄露不是未定义行为(UB) 。内存泄露固然不好,但它不会导致悬垂指针、数据竞争、读取未初始化内存等真正的安全问题。因此 mem::forget 本身是"安全"的。

实际用途mem::forget 最常见的用法是在 unsafe 代码中转移资源所有权 。例如 Vec::into_raw_parts 在把 buffer 指针交给调用者后,需要 forget 掉原来的 Vec,否则 Vec 的析构函数会释放那块已经交出去的内存:

rust 复制代码
let v = vec![1, 2, 3];
let (ptr, len, cap) = v.into_raw_parts();
// 内部: mem::forget(v),防止 v 的析构函数释放 ptr 指向的内存
// 现在调用者负责管理 ptr

在 safe Rust 中直接调用 mem::forget 比较少见。但问题是,即使你不直接调用它,还有其他方式可以达到同样效果 ,比如通过 Rc/Arc 配合 RefCell 实现的内部可变性、丢弃线程 JoinHandle 导致 move 的 val 永不销毁等。《Rust 程序设计语言》官方文档也有提及这一点:rustwiki.org/zh-CN/book/...

因此,正是因为存在很多"safe 泄露"的途径,Rust 团队最初做出的决定就是:接受所有类型都可能被泄露,不在类型系统中区分"可泄露"和"不可泄露"的类型 。这个决定让当时的 Rust 语言本身保持了简洁性,但也关上了一扇门:任何依赖析构函数一定运行才能保证安全性的 API,在当前 Rust 中,不一定是完全 safe 的

2.4 Drop 的五个结构性缺陷

即便析构函数能保证运行,Drop trait 自身的设计也有严重局限:

rust 复制代码
pub trait Drop {
    fn drop(&mut self);  // 这就是它的签名,非常受限
}

缺陷一:不能返回值drop 返回 (),无法返回 Result 报告清理过程中的错误。如果文件关闭失败、数据库回滚失败,析构函数只能默默 panic 或吞掉错误:

rust 复制代码
impl Drop for DatabaseConnection {
    fn drop(&mut self) {
        // 如果断开连接失败怎么办?
        // 不能返回 Err(...),只能 panic 或 log
        if let Err(e) = self.disconnect() {
            eprintln!("disconnect failed: {e}"); // 只能这样
        }
    }
}

缺陷二:不能接受参数 。有些资源的清理需要额外信息,比如事务需要一个连接对象来 commit/rollback,但 drop(&mut self) 没有地方传入这些参数:

rust 复制代码
// 理想中我们想写这样的代码,但 Drop 不支持
impl Drop for Transaction {
    fn drop(&mut self, conn: &Connection) {  // 编译错误:签名不对
        self.rollback(conn);
    }
}

缺陷三:只有一种 Drop。一个类型只能有一个析构函数,不能有"commit-析构"和"rollback-析构"两种变体。

缺陷四:不能是 async 的 。在 async 上下文中,清理操作可能需要 .await(比如发送一条断开连接的消息),但 drop 是同步的。目前没有 async Drop

缺陷五:drop 只接受 &mut self,不接受 self 。因为 drop 拿的是可变引用而非所有权,它不能把自己的字段"移出来",只能借用访问。这限制了类型本身析构逻辑的表达能力。

还有很多,正是因为上面这些原因,导致了诸如 DMA 访问、事务、网络、数据库等一系列操作选择了使用自定义 drop 函数 + mem::forget 的逻辑。甚至还有一种典型的 workaround:"析构炸弹"模式:

rust 复制代码
impl Drop for CriticalResource {
    fn drop(&mut self) {
        // 如果正常消费了这个值,应该已经调用过 mem::forget
        // 走到这里说明值被异常丢弃了
        std::process::abort(); // "析构炸弹":直接崩溃
    }
}

impl CriticalResource {
    fn consume(self) {
        // 资源应该走自定义逻辑,正常销毁:做真正的清理工作
        do_cleanup(&self);
        mem::forget(self); // 阻止析构炸弹触发
    }
}

但这是一种动态检查手段,运行时才能发现问题,而 Rust 用户更想要编译时静态保证

2.5 线性类型如何解决这些问题

关键在于思维转变:不再依赖 Drop trait 做清理,而是让"清理"变成一个普通方法调用,但编译器强制你必须调用它。(当然,对于无泄露风险的常规对象,也可以直接让编译器添加 drop)

我们假想一个只实现了 Move(不实现 DestructForget)的类型:

  • 只能被移动(传递所有权)
  • 不能被 mem::forget(没有实现 Forget
  • 不能被隐式 drop(没有实现 Destruct,编译器不会在作用域结束时自动析构它)

这意味着你拿到这个值之后,唯一能做的就是把它传给某个接受 self 的方法,而这个方法就是你定义的"消费函数"。既然消费函数是普通方法,Drop 的所有限制都不存在了:

  1. 解决"不能返回值":消费函数是普通函数,返回类型任意。

    rust 复制代码
    struct FileHandle { fd: RawFd }
    impl Move for FileHandle {}
    
    impl FileHandle {
        pub fn close(self) -> io::Result<()> {
            let FileHandle { fd } = self;
            match unsafe { libc::close(fd) } {
                0 => Ok(()),
                // 关闭失败?返回 Err
                _ => Err(io::Error::last_os_error()),
            }
        }
    }
    
    fn work() -> io::Result<()> {
        let f = FileHandle::open("data.txt")?;
        f.write_all(b"hello")?;
        f.close()?;  // 编译器强制调用;close 失败会被 ? 传播
        Ok(())
        // 如果忘了 close,编译错误,不是运行时泄露
    }
  2. 解决"不能接受参数":消费函数签名完全自由。

    rust 复制代码
    struct Transaction { data: Vec<u8> }
    impl Move for Transaction {}
    
    impl Transaction {
        pub fn commit(self, conn: &Connection) -> Result<(), Error> {
            let Transaction { data } = self;
            conn.send(&data)
        }
        pub fn rollback(self, reason: &str) {
            let Transaction { data } = self;
            log::warn!("rollback: {reason}");
            // data 被 drop(Vec<u8> 实现了 Destruct)
        }
    }

    当前的 Rust 中,你只能把 Connection 存在 Transaction 的字段里以便 drop 时能访问,或者用全局/thread-local 变量绕过,都很丑陋。

  3. 解决"只有一种 Drop" :你可以定义任意多个消费方法,编译器只要求你调用其中一个

    rust 复制代码
    fn process(conn: &Connection) -> Result<(), Error> {
        let tx = Transaction::begin(conn)?;
        if validate(&tx)? {
            tx.commit(conn)?;     // 路径 A:提交
        } else {
            tx.rollback("invalid"); // 路径 B:回滚
        }
        // 两条路径都消费了 tx,编译通过
        // 如果某个分支遗漏了,编译错误
        Ok(())
    }

    理论上,编译器也应当去做控制流分析,保证在每个控制流分支中,tx 都必须被恰好消费一次。

  4. 解决"不能是 async" :消费函数可以是 async fn,这意味着这个类型只能在 async 上下文中被析构(被 .await 消费),如果你试图在 sync 代码中 drop 它,编译器会报错。

    rust 复制代码
    struct AsyncConnection { stream: TcpStream }
    impl Move for AsyncConnection {}
    
    impl AsyncConnection {
        pub async fn close(self) -> io::Result<()> {
            let AsyncConnection { mut stream } = self;
            stream.write_all(b"QUIT\r\n").await?;  // 发送断开消息
            stream.shutdown().await?;              // 等待关闭完成
            Ok(())
        }
    }

    今天 async Drop 之所以设计困难,根本原因是 Drop::drop 的签名是 fn drop(&mut self) 同步的、无返回值的、固定签名。但如果清理逻辑不再走 Drop,而是走普通的 async fn close(self),"async drop"问题就消失了,它从一个语言特性问题变成了一个普通的 API 设计问题。

  5. 解决"drop 只接受 &mut self" :消费函数接受 self(所有权),可以解构、移出字段。

    rust 复制代码
    impl Transaction {
        pub fn into_parts(self) -> (Vec<u8>, Metadata) {
            let Transaction { data, meta } = self;  // 解构,拿到每个字段的所有权
            (data, meta)  // 自由地把字段移出来返回
        }
    }

    今天的 Drop::drop(&mut self) 里,你不能写 let Transaction { data, .. } = self;,因为你只有 &mut self,不能移出字段(除非用 mem::take?)

因此,线性类型的核心思路是"把析构从隐式的编译器行为,变成显式的用户方法调用"。Drop 之所以有这么多限制,是因为它的调用时机和方式由编译器控制,编译器需要在任何可能的退出路径上插入 drop 调用,所以签名必须固定。而线性类型把这个控制权还给了用户:你来决定什么时候、怎么消费这个值,编译器只负责检查你确实消费了

2.6 历史的遗憾

2015 年,Rust 1.0 发布前几个月,thread::scoped API 被发现不安全 (Related Issue),用户可以通过 mem::forget 泄露 JoinGuard,跳过 join,导致 use-after-free。Rust 团队在时间压力下决定:不添加 Leak trait,接受所有类型都可以被泄露的现实,社区中被称为 "Leakpocalypse" 大事件,直接决定了 Rust 的设计哲学。这存在几个历史原因:

  1. 时间紧迫:当时距离 1.0 发布仅剩几个月,并且部分因素是因为 Mozilla 管理层给 Rust 团队施加了巨大的 1.0 发布压力。引入 Leak trait 需要对整个标准库和编译器类型系统进行大规模重构。
  2. 复杂性爆炸: 如果引入 Leak,几乎所有的泛型代码都需要考虑 T: Leak 约束。这会极大增加语言的学习成本和 API 的复杂性。
  3. 无法覆盖所有路径: 即使没有 mem::forget,通过 Rc 循环引用、故意制造死锁,依然可以导致析构函数不运行。

Rust 的一些设计者后来坦言:如果能回到 2015 年,他会同时添加 MoveLeak,而现在想补上这个遗憾极其困难,核心原因是向后兼容性,有这么几种可能的方案:

  1. Leak 作为 auto trait (像 Send):所有类型默认实现 Leak,不想被泄露的类型用 impl !Leak。其问题在于:给 mem::forget 加上 T: Leak 约束是 breaking change,因为今天所有写 mem::forget(x) 的泛型函数都没有 Leak bound。
  2. Leak 作为 ?Trait (像 Sized):默认所有泛型参数隐含 Leak,想支持线性类型的函数写 ?Leak。但问题可能更大:标准库中所有 stable 的关联类型都无法加 ?Leak bound,也是 breaking change。

社区目前也在探索更好的方案,比如通过 opt-in 的方式逐渐向后兼容,在不破坏现有代码的前提下逐步引入新约束。

3. Pin

3.1 Pin 解决了什么问题

当 Rust 引入 async/await 时,遇到了一个核心问题:async 函数的状态机可能是自引用的(包含指向自身数据的引用)。自引用结构不能被移动,否则内部指针会失效。

rust 复制代码
async fn self_reference() {
    let mut array = [1, 2, 3];
    let ptr = &array[0]; // ptr 引用了同函数内的 array
    some_async_op().await; // 挂起,状态机必须保存 array 和 ptr
    // 倘若 array 已经被移动掉了,那么 ptr 指向的地址就会失效
    println!("{}", *ptr);
}

// 状态机内部实现
struct AsyncStateMachine {
    array: [i32; 3],
    // 指针指向上方 array 字段
    ptr: *const i32,
}

由于没有 Move trait,Rust 用 Pin<&mut T> 来表达"这个值已经被固定,不能再移动了":

rust 复制代码
pub trait Future {
    type Output;
    // Pin<&mut Self> 表示 self 被"钉住"了
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

虽然 Pin 能用,但它带来了显著的复杂度:

  1. 概念复杂!Unpin(双重否定)表示"不能 unpin"即"被固定的类型"。很难理解 Pin<&mut T> 只在 T: !Unpin 时才有实际约束这种逻辑。

  2. Pin Projection 需要宏 。要安全地从 Pin<&mut MyStruct> 访问一个字段的 Pin<&mut Field>,需要使用 pin-project 等宏。

  3. 在 trait 签名中传染 。因为 impl Trait 语法无法表达"当 T 被 Pin 时实现某个 trait"的约束(无法在 bound 的左侧使用 Pin),Pin 必须直接出现在方法签名中:

    rust 复制代码
    // 因为 -> impl Future 无法表达 "Pin<&mut T>: Future" 的约束
    // 所以 Future::poll 必须把 Pin 写进签名
    trait Future {
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
    }
  4. trait 大量重复 如果想让 Iterator 支持自引用迭代器,就需要一个全新的 PinnedIterator trait。对于 std::io 模块,可能需要:Read / PinnedReadWrite / PinnedWriteSeek / PinnedSeek

3.2 Pin vs Move

Pin 把不可移动性视为 val 的属性,即一个值在被 Pin 之前可以移动,之后不行。而 Move 把不可移动性视为类型本身的属性,某些类型天生就不能移动。

rust 复制代码
// Pin:不可移动性是"val"的属性
fn foo<T: Unpin>(t: &mut T);          // 没有 Pin,可以移动
fn foo<T: !Unpin>(t: &mut T);         // 没有 Pin,仍然可以移动!
fn foo<T: !Unpin>(t: Pin<&mut T>);    // 有 Pin,才不能移动

// Move:不可移动性是"类型"的属性
fn foo<T: Move>(t: T);                // T 可以移动
fn foo<T: !Move>(t: &mut T);          // T 不能移动,但可以借用

Move 作为 auto-trait,可以像 Send/Sync 一样与任何其他 trait 自由组合,不需要重复定义 trait,Rust 社区也倾向于用 Move 替代 Pin

4. 构建线性类型系统

4.1 多层级类型系统定义:Forget / Destruct / Move / Pointee

社区提出了一个较为完整的 trait 层次结构,称为"Controlled Destruction":

rust 复制代码
trait Pointee {}          // 可以被指针引用
trait Move: Pointee {}    // 可以被"移动"(重定位)
trait Destruct: Move {}   // 可以被"析构"(drop)
trait Forget: Destruct {} // 可以被"遗忘"(泄露)

默认情况下,泛型参数拥有 Forget 约束(即所有当前行为不变)。通过降低约束级别可以获得更强的类型保证:

rust 复制代码
// 默认,等同于今天的 Rust
fn anything<T: Forget>(a: T, b: T, c: T) {
    std::mem::drop(a);   // OK:可以析构
    std::mem::forget(b); // OK:可以遗忘
    let x = c;           // OK:可以移动
}

// 只有 Destruct,不能 forget
fn must_cleanup<T: Destruct>(a: T, b: T, c: T) {
    std::mem::drop(a);   // OK
    std::mem::forget(b); // 错误!T: Forget 未满足
    let x = c;           // OK
}

// 只有 Move,不能 drop 也不能 forget
fn must_consume<T: Move>(a: T) -> T {
    // 不能 drop,不能 forget,只能移动
    a // 必须返回它
}
  • Pointee (可被指针引用):!Move 的极端形式,值从出生到死亡都在同一个内存位置,甚至不能放在栈上(因为栈会 pop)。适用于建模 video RAM 或 MMIO 寄存器等
  • Move (可被移动):值可以从一个内存位置移动到另一个。!Move 的类型有稳定的内存地址,永远不会被移动。
  • Destruct (析构函数保证运行):值不能被 mem::forget,但可以被正常 drop。这是线性类型的核心。
  • Forget (当前默认行为):值可以被 mem::forget 安全跳过析构,这是今天 Rust 中所有类型都隐式拥有的。

4.2 控制流的挑战

线性类型的"恰好使用一次"在有控制流(if/while/panic)时变得复杂。目前有三种强度的检查思路:

  • 最弱规则:变量在作用域内出现一次即可(不管控制流),但不安全,因为某个分支可能没有消费值。
  • 中间规则 :变量在每个控制流分支中都恰好使用一次。
  • 最强规则 :在程序的所有可能执行路径中都恰好使用一次。
rust 复制代码
// 最弱规则通过,但实际上不安全
fn bad(linear: Linear) -> Option<Linear> {
    if undecidable() {
        Some(linear) // linear 被使用了
    } else {
        None         // linear 没被使用!但 else 分支会自动析构它
    }
}

// 中间规则下,需要每个分支都消费 linear
fn good(linear: Linear) -> Option<Linear> {
    if undecidable() {
        Some(linear)
    } else {
        linear.consume(); // 显式消费
        None
    }
}

// 最强规则下,还需要考虑程序所有可能的运行状态:
// 包括无限循环、panic、甚至 IO 故障等情况
fn handle_file(f: File) {
    while some_infinite_process() {
        // 这里在做一些永不停止的事情
    }
    f.close(); 
}

panic 是最大的难题,如果函数调用可能 panic,线性值的作用域内就存在一条隐式的"提前退出"路径。在这条路径上,值会被 RAII 析构,但这可能不满足线性类型的安全条件。对于这种情况的解决方案是引入不会 panic 的函数标注(即后面的"函数效果"):

rust 复制代码
#[no_panic]
fn safe_operation() {
    // 保证不会 panic
}

fn handle_file(f: File) {
    // 编译器知道这一行一定不会失败
    safe_operation()
    f.close(); 
}

5. Effects、Refinement Types、Pattern Types

5.1 Effect Types

Rust 已经有几种"函数效果":

rust 复制代码
const fn foo() {}  // 编译时可求值
async fn bar() {}  // 异步
// nightly:
gen fn baz() {} // 生成器
try fn qux() {} // 可失败

函数的效果声明了函数本身包含的特性和约束条件(如 async 只能在 async 函数中调用),当然,除此以外,Rust 还可以有更多的效果,如:

  • no_panic: 保证函数不会出现 panic,线性类型系统可以依照这一依据来对控制流进行分析(见上文)
  • no_io:保证不会因为调用 IO 产生副作用。更进一步,可以有 no_host 保证不会去调用宿主主机 API

5.2 Refinement Types 更精确的类型

Pattern types 用 pattern 语法给已有类型附加约束,说实话,有点开始像 TypeScript 了:

rust 复制代码
// 用 pattern 约束定义 NonZeroUsize,不再需要特殊的编译器优化
type NonZeroUsize = usize is 1..;

// 更多例子
type Percentage = u8 is 0..=100;
type AsciiChar = u8 is 0..=127;

5.3 View Types

View types 让编译器理解"不重叠的部分借用",从而能够更方便的分散持有可变引用:

rust 复制代码
struct Foo { a: String, b: String }

// 现在不行,编译器不知道 &mut self.a 和 &mut self.b 不冲突
// 有了 view types,可以同时持有两个不同字段的可变引用
fn process(foo: &mut {a} Foo, bar: &mut {b} Foo) {
    // 分别处理 a 和 b
}

5.4 类型系统各元素关系

这些类型系统特性之间存在关联,它们都是为了保证在编译期获取到更丰富的信息来支撑 Rust 的安全性。

  1. Effect types 回答这个函数能做什么(能 panic 吗?能做 IO 吗?能异步吗?)
  2. Substructural types 回答这个值能怎么用(能丢弃吗?能移动吗?能复制吗?),如,线性类型。
  3. Refinement types 让编译器知道这个类型有什么约束(非零?在某个范围内?)
  4. View Types 让编译器知道"部分借用",能够更好的进行 RAII 推断。

参考资料

相关推荐
Rust研习社2 小时前
Rust Tracing 实战指南:从基础用法到生产级落地
rust
分布式存储与RustFS3 小时前
MinIO迎来“恶龙”?RustFS这款开源存储简直“不讲武德”
架构·rust·开源·对象存储·minio·企业存储·rustfs
数据知道16 小时前
claw-code 源码分析:从 TypeScript 心智到 Python/Rust——跨栈移植时类型、边界与错误模型怎么对齐?
python·ai·rust·typescript·claude code·claw code
Rust研习社1 天前
深入浅出 Rust 迭代器:从基础用法到性能优化
rust
@atweiwei1 天前
langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)
开发语言·rust·langchain·agent·向量数据库·rag
skilllite作者1 天前
自进化 Agent 的 skills 别长成烟囱:从多入口分叉到统一发现与 spec 防火带
人工智能·算法·rust·openclaw·agentskills
Rust研习社2 天前
关于 Rust Option 的那些事:从基础到常用 API 全解析
rust
爱分享的阿Q2 天前
Rust加WebAssembly前端性能革命实践指南
前端·rust·wasm
沉淀粉条形变量2 天前
rust 单例模式
开发语言·单例模式·rust