Skip to content

Rust 内存管理与所有权

栈与堆

栈 vs 堆

  • 数据存储时:栈比堆快 ( 压栈不需要寻找存储空间 )
  • 数据查询时:栈比堆快 ( 堆需要通过指针才找到对应的数据 )

栈内存 ( Stack )

大小固定的、已知的数据存储在栈上

说明
存入数据 ( 压栈 / 入栈 )按照值的接收顺序存储
移除数据 ( 弹栈 / 出栈 )按照值的接收的相反顺序释放

堆内存 ( Heap )

编译时大小不固定、值会动态变化的数据存储在堆上

说明
存入数据 ( 分配 )系统会自动寻找足够大的内存空间进行存储,并返回一个指向内存空间的指针
指针的大小固定可以存储在栈上

所有权

Ownership

Rust 没有垃圾回收机制,而是通过所有权机制来管理内存

所有权规则

  • 每个值都有一个变量,该变量为该值的所有者
  • 每个值只有一个所有者
  • 当值的所有者离开作用域时,该值被自动销毁

移动

Move

  • 堆中分配的数据被赋值给一个新变量时
  • 堆中分配的数据作为参数被传入一个函数时

数据的所有权会移动,原所有者被销毁无法再访问

若想保持数据的原所有者的所有权可以:克隆原所有者、引用原所有者

rs
fn main() {
    let a: String =  String::from("hello");     
    let b: String = a;  // a → b

    println!("{}", a);  // 报错: borrow of moved value: `a` value borrowed here after move
    println!("{}", b);  // hello
}
rs
fn main() {
    let s: String =  String::from("hello");     
    let l = handler(s); // s → handler函数内部

    println!("{}", s);  // 报错: borrow of moved value: `s` value borrowed here after move
    println!("{}", l);  // hello
}

fn handler(s: String) -> usize {         
   s.len()
}

复制

Copy

栈中存储的数据被赋值给一个新变量时仅值被复制,不会发生所有权转移,原所有者仍可被访问

rs
fn main() {
    let x:&str = "hello";           
    let y: &str = x;

    println!("{}", x);  // hello
    println!("{}", y);  // hello
}

克隆

Clone

堆中分配的数据可通过clone()方法进行克隆,新旧变量的所有权相互独立

rs
fn main() {
    let a: String =  String::from("hello");     
    let b: String = a;                          
    let b: String = a.clone();                  

    println!("{}", a);  // hello
    println!("{}", b);  // hello
}

引用

Reference

  • 堆中分配的数据的引用被赋值给一个新变量时,不会发生所有权的移动
  • 堆中分配的数据作为参数被传入一个函数时,不会发生所有权的移动

引用可理解为一个指向目标数据的指针

引用默认不可变,不能直接修改引用的数据,但是可变引用&mut可以修改

rs
fn main() {
    let a: String =  String::from("hello");
    let b: String = a;                          
    let b: &String = &a;                        

    println!("{}", a);  // hello
    println!("{}", b);  // hello
}
rs
fn main() {
    let s: String =  String::from("hello");         

    let l = handler(s);                             
    println!("{}", s);   // 报错: borrow of moved value: `s` value borrowed here after move

    let l = handler_keep_ownership(&s);             
    println!("{}", s);  // hello
}

fn handler(s: String) -> usize {                    
   s.len()
}
fn handler_keep_ownership(s: &String) -> usize {    
   s.len()
}

可变引用

Mutable Reference

rs
fn main() {
    let mut a: String =  String::from("hello");     
    let b: &mut String = &mut a;

    b.push_str(" world");
    println!("{}", b);  // hello world
    println!("{}", a);  // hello
}
rs
fn main() {
    let mut s: String =  String::from("hello");   
    let l = use_and_change(&mut s);
    println!("{}", l);      // 11
}

fn use_and_change(s: &mut String) -> usize {      
    s.push_str(" world");
    s.len()
}

为了防止数据竞争,一个作用域内只能有一个可变引用

rs
fn main() {
    let mut a: String = String::from("hello");
    let b: &mut String = &mut a;
    let c: &mut String = &mut a;  // 报错: cannot borrow `a` as mutable more than once at a time second mutable borrow occurs here
    println!("{} {}", b, c);
}
rs
fn main() {
    let mut a: String = String::from("hello");
    let b: &mut String = &mut a;

    println!("{}", a);  // 报错: cannot borrow `a` as immutable because it is also borrowed as mutable immutable borrow occurs here
    println!("{}", b);
}

借用

Borrow

函数将数据的引用作为参数时被称为借用,不会发生参数的所有权的移动,原所有者仍可被访问

在函数内无法直接修改引用,但是借用的是可变引用时则可修改

rs
fn main() {
    let s1: String =  String::from("hello");    
    let l = only_use(&s1);

    let mut s2: String =  String::from("hello");
    let l = use_and_change(&mut s2);
    println!("{}", l);      // 11
}

fn only_use(s: &String) -> usize {              
    s.push_str(" world");   // 报错
    s.len()
}

fn use_and_change(s: &mut String) -> usize {    
    s.push_str(" world");
    s.len()
}