(Rust笔记)Rust内存管理核心:Ownership

Ownership是一个新概念,编程语言,特别是分/实时操作系统的编程语言,大多数时候都在跟内存打交道。就拿汇编来说,在汇编语言里,需要通过寻址来获取内存里面存储的数据,不管是立即数寻址,寄存器寻址还是变址寻址,都在和内存打交道。而现代高级的编程语言都建立在汇编之上。

在高级语言中,计算机科学家和优秀的程序员们为了减少内存管理这一繁琐操作,提供了很多内存管理机制。

在内存里,有(Stack)(Heap)之分。一般而言,栈的存取开销低于堆的,因为栈是定长,先进后出的数据结构,由操作系统统一管理;而堆是不定长的,由程序员管理。在C/C++中,我们就要手动管理内存(分配和释放)。为了简化这一过程,一些构建在C之上的语言提出了虚拟机的概念,包括JVM平台,Python、Ruby、JavaScript等解释型语言。

鉴于此,Rust提出了第三种管理内存的概念,即Ownership。Rust没有臃肿的运行时支撑,同时也要尽力避免由于直接操作内存而带来的问题——容易产生隐患和调试指针而带来的时间浪费。

Ownership

Ownershipt基本原则:

  1. Rust中的每一个变量都有它的Owner;
  2. 每个变量同一时间只能有一个Owner;
  3. 当变量的Owner离开了作用域(Scope),这个变量将被释放。

Rust的作用域通过花括号来指明(“{ … }”),花括号可以存在于大多数地方,包括但不限于函数定义,arm调用,表达式,控制语句。

Ownership之标量

对于标量,即存放于内存的中,《TRPL 2ed》有一个例子:

{                      // s is not valid here, it’s not yet declared
    let s = "hello";   // s is valid from this point forward

    // do stuff with s
}                      // this scope is now over, and s is no longer valid

上例中,s声明后,在作用域内即生效,离开作用域即被编译时(Compile Time)释放。

Ownershipt之mv操作

Move操作只针对堆变量,它们初始化时被分配于内存的中,赋值时,Ownershipt会发生转移。

Rust不像没有运行时的C++,C++对象赋值只能操作指针或重载赋值运算符;也不像具有运行时的JavaScript,JavaScript对象赋值默认为引用,因为有自动垃圾回收机制保护内存。

Rust为系统级编程语言,意味着他要直接与内存打交道,而且要保证其编制的程序运行速度与C接近。于是Ownership这一概念发挥了重要作用,看下面摘自《TRPL 2ed》的例子。

Rust标量赋值不是转移Ownership,而是将新值push进栈:

let x = 5;
let y = x;

而堆变量赋值为了减少内存开销(特别是一个占用很大堆空间的变量),采用Ownership转移:

let s1 = String::from("hello");
let s2 = s1;                    // s1 has been invalidated

String s1赋值到s2内存变化情况(摘自《TRPL 2ed》)如下图,其中将s1赋值给s2的过程中,s1的Ownership转移给s2,s1默认不可用了。

rust(s1 to s2)
rust(s1 to s2)

Ownership之cp

如果硬是要完整复制整个堆给新变量,需要用copy方法,即以下代码是合法的(摘自《TRPL 2ed》):

let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

Ownership与函数

以下两例摘自《TRPL 2ed》,原作者已经打上注释,很能说明问题。

without return values

fn main() {
    let s = String::from("hello");  // s comes into scope.

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here.
    let x = 5;                      // x comes into scope.

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it’s okay to still
                                    // use x afterward.

} // Here, x goes out of scope, then s. But since s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope.
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope.
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

with return values

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1.

    let s2 = String::from("hello");     // s2 comes into scope.

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3.
} // Here, s3 goes out of scope and is dropped. s2 goes out of scope but was
  // moved, so nothing happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it.

    let some_string = String::from("hello"); // some_string comes into scope.

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function.
}

// takes_and_gives_back will take a String and return one.
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope.

    a_string  // a_string is returned and moves out to the calling function.
}

在以上两例中,调用函数时,如果传入的变量为堆变量,则变量的Ownership将会默认被转移进函数体内,如果要再次获得Ownershipt,则必须返回变量值。

这样做显然不方便,所以有Referrences和Borrowing这两个概念。

Refferences和Borrowing

Borrowing即为引用的函数形参。下例摘自《TRPL 2ed》:

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(s1: &mut String) {
    s1.push_str(", world");
}
Rust Refference(s to s1)
Rust Refference(s to s1)

在引用中,要注意,变量做多次引用只能做常量引用;如果要做变量引用,则只能引用一次。

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // Error. We couldn't borrow the mutable refference twice

/*****************************************/

let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM

更多Refference和Borrowing还是参考《TRPL 2ed》,这里只做了简单笔记。

Slices

关于slices,摘一个来自《TRPL 2ed》的例子作为本文结尾:

let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];
Rust Slice
Rust Slice

slice其时也是引用,只是引用的特殊情况,详见《TRPL 2ed》。

参考:

《TRPL 2ed》

作者: YanWen

Web 开发者

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google photo

You are commenting using your Google account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s