Rust Ownerships Lifetimes 教程

20-02-22 编程 #code #rust

some notes on rust ownership,reference,string and &str, and lifetimes

rust ownership

//heap and stack: stack is store data that known,fixed size.
//memory manager keeping track of what parts of code are using what data on the heap, 
//minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap
//so you don’t run out of space are all problems that ownership addresses.

//ownership rules:
// Each value in Rust has a variable that’s called its owner.
// There can only be one owner at a time.
// When the owner goes out of scope, the value will be dropped.

// stack only data(栈内数据) assignment will make a copy operation, since it is fixed size, the copy is fast
// rust use h.clone() make a heap data deeply copy.
// impl the Copy trait can make the original variable still usable after assignment.
// Copy trait can not use with Drop trait, Drop 可以理解为 destructor,当数据超过自己的 scope 时,drop() 方法被调用;
fn copy() {
    let x = 5;
    let y = x; //copy the value(5) in the stack,since it is fixed-size, the copy operation is fast

    let s1 = String::from("hello"); //String 和 &str 区别见后文
    let s2 = s1; //now s1 is invalid
    // println!("{}, world!", s1); //error, the "hello" ownership move to s2

    let s3 = s2.clone(); //copy the heap value("hello"), String impl the Clone trait
    println!("{}, world!", s2); //s2 still usable
}

// passing function arguments or return value by function is same as 
// assigning a value to a variable, you need take care the ownership of heap value,
fn ownership() {
    let x = 5;
    let x10 = plus10(x);//x still usable since the x is stack data
    println!("{}", x);
    println!("{}", x10);

    let s = String::from("hello");
    takes_ownership(s); //s's value moves into the function and so is no longer valid here
    //println!(s) ;//error!
}

fn plus10(i: i32) -> i32 { // since the i is primitive in stack, so the function return a new value  
    i + 10 
}

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.

推荐阅读A closer look at Ownership in Rust

References and Borrowing:

// since the ownership is too hard to track by coder's eye, rust introduce the ref and borrowing
// a function that accept a ref will not takeover a value's ownership when the function is called
// also will not drop the value's backend memory when function is return.


// a variable can only have one mut ref or many immutable ref in a same scope;

//dangling reference
fn dangle() -> &String {
    let s = String::from("dangle ref");
    &s //error
}// the s is dropped, but the function try to return s reference


### String vs str vs &String vs &str
//1. String is heap string buffer
//2. &String is a ref of String
//3. str is unknown immutable sequence of utf8 bytes stored somewhere in memory. the memory may be:
//  3a. in binary: a string literal "foo" is a &'static str. The data is hardcoded into the executable and loaded into memory when the program runs.
//  3b. in heap: String implement Deref<Target=str>, and so inherit all of str's methods.
//  3c. in stack: when use str::from_utf8(x).unwrap(); x is stack-value ref

//> the &str param can accept a &String since the String implement Deref<Target=str>.
// 即接受&str 的地方都可以使用&String

//!!! since the str is unknown size, one can only use it by &str, called slice. slice is a view of some data. 


fn str_demo() {
    let s = "hello str";//The type of s here is &str: it’s a slice pointing to that specific point of the binary.
    // This is also why string literals are immutable; &str is an immutable reference.
    let mut string = s.to_string(); //&str to String
    string.push_str(" append");
    println!("{}", string);
}

//a slice has static lifetime
let s = "hello";
//means
let s: &static str = "hello";

lifetimes are only about reference

a ref must die before its referent

in rust:

// No inputs, return a reference
fn function1<'a>() -> &'a str {}

// Single input
fn function2<'a>(x: &'a str) {}

// Single input and output, both have the same lifetime
// The output should live at least as long as input exists
fn function3<'a>(x: &'a str) -> &'a str {} // no need the lifetime annotation,lifetime elision

// Multiple inputs, only one input and the output share same lifetime
// The output should live at least as long as y exists
fn function4<'a>(x: i32, y: &'a str) -> &'a str {}

// Multiple inputs, both inputs and the output share same lifetime
// The output should live at least as long as x and y exist
fn function5<'a>(x: &'a str, y: &'a str) -> &'a str {}

// Multiple inputs, inputs can have different lifetimes 🔎
// The output should live at least as long as x exists
fn function6<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {}

lifetimes in struct/enum

// Single element
// Data of x should live at least as long as Struct exists
struct Struct1<'a> {
    x: &'a str
}

// Multiple elements
// Data of x and y should live at least as long as Struct exists
struct Struct2<'a> {
    x: &'a str,
    y: &'a str
}

// Variant with a single element
// Data of the variant should live at least as long as Enum exists
enum Enum<'a> {
    Variant(&'a Type)
}