Rust核心概念

86 阅读4分钟

这期我们来唠唠 Rust 的“核心五大护法”,这几个概念是 Rust 世界的地基,理解它们之后,你再看 Rust 就像“武林秘籍看目录”,豁然开朗。我会从“前端视角”来比喻,结合 JS/TS 帮你建立联系。


🥇 所有权(Ownership)——Rust 的“内存保安队长”

在 JS 里,你从来没管过内存,new 也好,数组也好,变量用完就等 GC 回收。但 Rust 没 GC,它靠所有权系统来“自动管理内存”。

通俗讲:

在 Rust 里,每块内存只会有一个“所有者变量”。当所有者变量离开作用域,这块内存就自动释放。

举个例子:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1 把所有权给了 s2,自己就不能用了
    println!("{}", s1); // ❌ 编译报错:s1 已被移动
}

就像 JS 里的变量 s1 = 'hello';你把它“转手给了 s2”,但 Rust 会说:“那 s1 就不能用了哈,我要保证你不会用到无效数据”。

编译期强制你安全编程,这就是 Rust 的牛逼之处。


🥈 借用(Borrowing)——变量“出借”,但不能“转让”

那你可能说:“我不想把变量完全交出去,只是借它用一下。”这时候就轮到 Rust 的“借用”机制了。

类比 JS:

function double(arr) {
  return arr.map(x => x * 2);
}

你传的是个引用,对吧?Rust 里也能借用变量,不转移所有权:

fn double(arr: &Vec<i32>) {
    for x in arr {
        println!("{}", x * 2);
    }
}

fn main() {
    let v = vec![1, 2, 3];
    double(&v);  // 借用 v,不移动所有权
    println!("{:?}", v); // ✅ OK,v 还能用
}

Rust 区分:

  • &T:不可变借用,多个可以共存
  • &mut T:可变借用,只能有一个

核心思想:你要么多个读者,要么一个作者,不能又读又写,这样就不会数据冲突。


🥉 生命周期(Lifetimes)——借用的“生死时限”

这听起来吓人,其实是为了解决一个问题:你借的东西,不能比原来活得久。

想象下这个 JS 场景:

let result;
{
  const temp = "hello";
  result = temp;  // temp 出作用域就挂了,但你还在用 result
}
console.log(result);  // ❌ 潜在问题

Rust 编译器会捕捉这种情况,它要你声明:这个借用能活多久

fn get_first<'a>(s: &'a str) -> &'a str {
    &s[0..1]
}

'a 就是生命周期标注,它告诉编译器:传进来的引用,返回值的生命周期不能超过它。

你平时用编译器推断就够了,出问题再显式标注。


🟦 匹配模式(match)——比 switch-case 高级一百倍

JS 有 switch-case,Rust 有 match,但它不仅能匹配值,还能解构、匹配类型、绑定变量,非常强。

fn main() {
    let x = Some(5);
    match x {
        Some(1) => println!("one"),
        Some(n) => println!("value is {}", n),
        None => println!("none"),
    }
}

像是类型 + 模式匹配的结合体,有点像 JS 里的:

const x = { type: 'Some', value: 5 };
switch (x.type) {
  case 'Some': console.log(x.value); break;
  case 'None': console.log('none'); break;
}

但 Rust 写法更简洁,特别适合和枚举/错误处理配合使用


🟥 枚举 + 错误处理(Option / Result)——告别 null、try-catch 的新姿势

Rust 没有 nullundefined 这些让人崩溃的玩意儿。取而代之的是:

1. Option<T> 表示可能有值,也可能没值:

let name: Option<String> = Some("Tom".to_string());

match name {
    Some(n) => println!("Hello, {}", n),
    None => println!("No name"),
}

2. Result<T, E> 表示可能成功,也可能失败(比如文件读取、网络请求):

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10, 2) {
        Ok(val) => println!("Result: {}", val),
        Err(e) => println!("Error: {}", e),
    }
}

这种做法好在哪?

  • 编译器强制你“必须处理错误
  • 再也不会有“调用了 null 的 toString 报错”这种事

是不是很像 TS 的 EitherOption 泛型类型,但更原生、更好用?


总结:核心概念速查表

概念通俗解释JS 类比
所有权谁负责释放内存没有 GC,要自己守规则
借用引用变量但不转让像引用参数,JS 默认传引用
生命周期借用变量活多久闭包/作用域变量别用晚了
匹配模式花式 switch-case解构 + 类型判断的组合拳
Option / Result安全处理空值和错误告别 null、try-catch 的未来式