Logo

Rust中的所有权与借用

Rust 所有权与借用知识总结

堆与栈的基础概念

栈(Stack)​​

​​存储方式​​:后进先出(LIFO),按顺序存储和释放数据。 ​​数据特点​​:存储固定大小的已知数据(如基本类型、指针等)。 ​​性能​​:分配和释放速度快(仅操作栈顶),无需动态内存管理。 ​​示例​​:函数调用时参数、局部变量入栈,函数结束自动出栈。

堆(Heap)​​

​​存储方式​​:动态分配,需手动请求和释放内存空间。 ​​数据特点​​:存储大小未知或可能变化的数据(如 String、集合等)。 ​​性能​​:分配速度较慢(需操作系统查找可用空间),需跟踪内存生命周期。 ​​示例​​:String::from("hello") 在堆上分配内存,栈中存储指向堆的指针。


一、所有权的核心规则

  1. 核心规则
    • 每个值有且仅有一个所有者(变量)。 • 当所有者离开作用域时,值会被自动释放(调用 drop)。 • 赋值或传参时,默认会转移所有权(称为 移动)。

  2. 示例说明

    let s1 = String::from("hello");
    let s2 = s1; // s1 的所有权被移动到 s2,s1 不再有效
    // println!("{}", s1); // 编译错误:s1 已失效
    

二、所有权转移与复制

  1. 移动(Move)
    • 默认行为:赋值或传参会转移所有权,原变量失效。 • 适用于堆分配的数据(如 StringVec)。

  2. 复制(Copy)
    • 基本类型(如 i32bool)实现 Copy trait,赋值时直接复制值。 • 栈上数据直接复制,无所有权转移。

    let x = 5;
    let y = x; // 复制值,x 和 y 均有效
    
  3. 显式深拷贝(Clone)
    • 使用 clone 方法复制堆数据:

    let s1 = String::from("hello");
    let s2 = s1.clone(); // 深拷贝,s1 和 s2 均有效
    

三、函数与所有权

  1. 传参与返回值
    • 传参时所有权可能被转移:
    fn take_ownership(s: String) { /* ... */ }
    let s = String::from("hello");
    take_ownership(s); // s 的所有权转移给函数
    // s 不再可用
    
    • 函数返回时转移所有权:
    fn create_string() -> String {
        String::from("new")
    }
    let s = create_string(); // 所有权转移到 s
    

四、引用与借用机制

  1. 不可变引用(&T)
    • 允许只读访问数据,不转移所有权。 • 同一作用域内可存在多个不可变引用:

    let s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    println!("{} {}", r1, r2); // 允许
    
  2. 可变引用(&mut T)
    • 允许修改数据,同一时刻仅允许一个可变引用。 • 不可与不可变引用共存:

    let mut s = String::from("hello");
    let r1 = &mut s;
    // let r2 = &mut s; // 编译错误:同一作用域只能有一个可变引用
    // let r3 = &s;     // 编译错误:不可变与可变引用共存
    
  3. 借用规则(借用检查器)
    规则一:任意时刻,要么只能有一个可变引用,要么有多个不可变引用。 • 规则二:所有引用必须始终有效(避免悬垂引用)。


五、避免悬垂引用

  1. 生命周期保证
    Rust 编译器确保引用不会指向已释放的内存:
    // 错误示例:返回局部变量的引用
    fn dangle() -> &String {
        let s = String::from("hello");
        &s // 编译错误:s 离开作用域后被释放
    }
    // 正确做法:返回所有权
    fn no_dangle() -> String {
        String::from("hello")
    }
    

六、引用作用域优化(NLL)

  1. Non-Lexical Lifetimes (NLL)
    • 引用作用域在最后一次使用时结束(而非花括号结束)。 • 允许更灵活的借用:
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    println!("{} {}", r1, r2); // r1、r2 作用域结束
    let r3 = &mut s; // 允许,因为 r1 和 r2 已不再使用
    

关键总结

特性所有权借用
核心机制通过编译时检查确保内存安全,避免 GC 和手动管理通过引用(&T/&mut T)临时访问数据,不转移所有权
数据竞争防护单所有者机制避免多线程数据竞争同一时刻只能有一个可变引用或多个不可变引用,防止读写冲突
性能影响无运行时开销引用为指针操作,性能等同于 C/C++
典型场景管理堆数据(如 StringVec函数传参、避免深拷贝、临时修改数据

通过所有权和借用机制,Rust 在编译期解决了内存安全问题,同时兼顾性能与灵活性。

© 2025 All rights reservedBuilt with Flowershow Cloud

Built with LogoFlowershow Cloud