The Go Blog

Rust language

bantana
15 April 2020

Expression 表达式

Place Expression, 位置表达式, LeftValue, 左值

 位置表达式 就是 表示内存地址的表达式。通常包含一个name和内存地址.

1. 本地变量
2. 静态变量
3. 解引用 (*expr)
4. 数组索引 (expr[expr])
5. 字段引用 (expr.field)
6. 位置表达式组合

 通过位置表达式可以对某个数据单元的内存进行读写.主要是进行写操作,这也是位置表达式可以被赋值的原因.

位置表达式 通常在作用域范围内有一个name和一个内存地址.从语义来看,位置表达式在作用域范围内有持久的状态.

Value Expression, 值表达式, RightValue, 右值

 值表达式一般是临时数据.值表达式要么是字面量,要么是表达式求值过程中创建的临时值.

例如:

字面量:
    "hello,world"
    123
    
&p p变量的的内存地址

1+2

值在内存中只是一个内存中的内容而已,比如 "hello,world" , 只是存放在某个内存地址的内容;你不能对内容进行写操作,也就是你不能对一个不是内存地址的地方写操作.

go:

var x string = "hello, world";
x := "hello, world";
这在go中表示,在内存中有一个内存区域的内容是"hello, world",把他的地址赋给x;

c:

int x = 0;
int* p = &x;  // p是一个指针类型,指向的数据类型是int, p中存放的是x的地址值(&x);
*p = 5;  // *p 是一个指向p中存放地址的指针. 所以是位置表达式,可以被赋值;

但是

&x = 3; // [clangd] Expression is not assignable [Error]
&x 是取x变量的地址值,并不代表指向这个地址的指针.所以他只能是一个RightValue.

变量的所有权

let x = "hello".to_string();
let <x: LV> = <"hello".to_string(): RV>;
let <LV> = <RV>
LV = RV
RV是内存中的一个真实内存区域的值,这个RV的所有权的管理问题;

lv1 = <RV>
lv2 = <RV>
...
lvn = <RV>

如果RV被其中一个修改,或删除,那么其他位置访问这个RV就有可能出各种未期望的状况;
我们知道一个值都是读取操作没有什么安全问题;
但是我们想限制只有一个人拥有写这个内存区域的所有权.

看一个所有权move后的访问错误:

let x2 = "hello".to_string();
// println!("addr: {:p}, value: {}", &x2, x2);
let y2 = x2;
// println!("addr: {:p}, value: {}", &y2, y2);

println!("addr: {:p}, value: {}", &x2, x2);

// error[E0382]: borrow of moved value: `x2`
//   --> src/main.rs:32:39
//    |
// 27 |     let x2 = "hello".to_string();
//    |         -- move occurs because `x2` has type `std::string::String`, which does not implement the `Copy` trait
// 28 |     println!("addr: {:p}, value: {}", &x2, x2); // addr: 0x7fff752ee650, value: hello
// 29 |     let y2 = x2;
//    |              -- value moved here
// ...
// 32 |     println!("addr: {:p}, value: {}", &x2, x2);
//    |                                       ^^^ value borrowed here after move
//
// error: aborting due to previous error
//
// For more information about this error, try `rustc --explain E0382`.

解释下编译器发现的问题;

27行单独执行没什么问题;
29行接着执行也没什么问题;这个只是把x2对<RV: "hello".to_string();>的所有权move给了y2, 这时候x2和他的RV都被析构掉了;
32行这时候访问了&x2; 得到一个错误 你在x2 所有权move后,使用了 &x2 试图访问,在这之前这个x2和他的RV都不存在了;
这时候回到27行错误提示:
编译器认为你如果试图使用32行的代码,除非27行这个x2的type实现了'Copy'的trait;

在这个页面底部有一个实现这个 'Copy' trait 的例子, 看后面的example: ex1.rs

通常语言的设计者会根据实际使用情况来给一些常规类型实现默认Copy的trait;

比如 ex2.rs 例子中实现了i32类型的默认Copy trait.

fn main() {
    let x: i32 = 8;
    let y: i32 = x;
    println!("y {}", y);
    println!("x {}", x);
}
// y 8
// x 8

传值(COPY) or 传引用

传值的特点就是值COPY一份,

传引用就是传一个指针地址,

理解这个问题的重心是理解你要对数据做什么处理,要如何更高效或更有效的使用数据;

如果需要对数据做修改处理,但是要保留一份原始数据,就需要使用 值COPY, 如果只是需要对数据做读取处理,可以放心的使用引用, 如果需要对原始数据做修改处理,可以选择引用.这个需要注意的是会不会引起其他问题.

传值(COPY)

1. 原始变量和内容会被复制一份,内部生成同名但是在函数体作用域有效;
2. 有些复杂结构体内部会使用指针,为避免错误修改原数据,需要对数据做 "深COPY";
当然这样做是有代价的,你需要评估 costs (内存开销,性能问题).

传引用(传递本身)

1. 传引用开销很小,通常就是一个指针长度.
2. 只是对数据的读取操作,应该优先使用这种方式,可以理解为只读引用;
3. 如果需要对原数据修改就需要认真评估这个问题了.因为可能存在数据竞态和数据竞争的问题.;

 竞态指多个并发处理对相同的内存区域(临界区)出现"修改"行为。

 数据竞争是指一个在写这个内存区域,而另一个在读这个内存区域,这两者就处于数据竞争行为。

Shadowing

// Shadowing
fn main() {
    let y = 2;
    println!("addr: {:p}, value: {}", &y, y); // addr: 0x7ffe8322e8dc, value: 2
    let y = 3;
    println!("addr: {:p}, value: {}", &y, y); // addr: 0x7ffe8322e95c, value: 3

}

在这个例子中隐藏了几个问题:

1. let 是不可变,那么为什么可以第二次使用 let redefine? 我们知道在一些语言中const是不能 redefine 的.为什么rust允许;这个问题要看设计者如何评估;

一种设计思路是禁止这样使用,简化,并避免这种隐藏的坑;
一种思路是我认为你可以这么使用,前提是你知道这么做是在干嘛.

很多老语言的设计者可能就没考虑过这个问题.

2. rust中不可变变量 y 的地址和内容在第2次 使用 [let y = 3;] 时候都被更改了,这种就叫做shadowing, 也就是第一个 y 的地址和内容都被Shadowing(屏蔽?)了,那原来的y地址和内存中的内容块是怎么处理的呢? 我没有深究这个,但你可以考虑下如何设计解决这个问题,

方案:

1. 编译器在编译的过程中加入了一段代码,在rebinding y (let y = 3;)之前,先加入一段针对之前 y 的析构代码;
2. 某些类型系统底层可以扩展;
3. 不管,如果是stack变量,等stack退出的时候自动清除?如果包含有heap的分配怎么办?只能依赖某些memory leak分析工具;

example

ex1.rs

struct Count {
    data: i32,
    value: i32,
}

impl Clone for Count {
    fn clone(&self) -> Count {
        return Count {
            data: self.data,
            value: self.value,
        };
    }
}

impl Copy for Count {}

fn main() {
    let mycount = Count {
        data: 10,
        value: 22,
    };
    let youcount = mycount;
    println!("mycount addr: {:p}", &mycount);
    println!("youcount addr: {:p}", &youcount);
    println!("mycount.data addr: {:p} {}", &mycount.data, mycount.data);
    println!("youcount.data addr: {:p} {}", &youcount.data, youcount.data);
    println!("mycount.value addr: {:p} {}", &mycount.value, mycount.value);
    println!(
        "youcount.value addr: {:p} {}",
        &youcount.value, youcount.value
    );
    //
}

// mycount addr: 0x7ffd69a07308
// youcount addr: 0x7ffd69a07310
// mycount.data addr: 0x7ffd69a07308 10
// youcount.data addr: 0x7ffd69a07310 10
// mycount.value addr: 0x7ffd69a0730c 22
// youcount.value addr: 0x7ffd69a07314 22

简单的也可以使用编译器指令 #[derive(Copy, Clone)] 来帮我们自动实现Copy, Clone 的 trait

#[derive(Copy, Clone)]
struct Count {
    data: i32,
    value: i32,
}

fn main() {
    let mycount = Count {
        data: 10,
        value: 22,
    };
    let youcount = mycount;
    println!("mycount addr: {:p}", &mycount);
    println!("youcount addr: {:p}", &youcount);
    println!("mycount.data addr: {:p} {}", &mycount.data, mycount.data);
    println!("youcount.data addr: {:p} {}", &youcount.data, youcount.data);
    println!("mycount.value addr: {:p} {}", &mycount.value, mycount.value);
    println!(
        "youcount.value addr: {:p} {}",
        &youcount.value, youcount.value
    );
}
/*
    Borrowing and Ownership in Rust
*/

/*
  Stack
  - Extremely Fast
  - Values must have Fixed Sizes
  - Always puts data in on Top
  - data pushed down as new data comes in
*/

/*
  Heap
  - Less Organized and Slower
  - Accepts Dynamicly Sized Data or Data that can Grow
  - Returns a Pointer which goes on the stack
  - Pointer points to where the data is on the Heap
*/

/*
Ownership Rules:
1) Each value has a variable which is its owner.
2) There can only be one owner at any given time.
3) When the owner goes out of scope, the value will be dropped out of memory.
*/

/*
Borrowing Rules:
    1) Allowed infinite borrows for readonly access.
    2) Readonly borrows make the original data immutable for their duration.
    3) Only allowed to pass one borrow at a time for write access/mutability.
*/

/* Rust Stack | Copy Types
    bool
    character
    numbers
    slices
    fix sized arrays
    tuples containing primitives
    function pointers
*/

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

    let mut a = &s[1..3];
    let b = &s[2..5];

    let c = &mut a;
    let d = &b;

    let y = false;
    let x = 10;

    own_and_borrow_stuff(&mut s, &y, x);
}

fn own_and_borrow_stuff(a: &mut String, b: &bool, c: u8) {}

// fn main() {
//     let mut a = String::from("A String");
//     {
//         let b = &a;
//         let c = &a;
//         let d = &a;

//         println!("{}, {}, {}", b, c, d);
//     }
//     a.pop();
//     {
//         let x = &mut a;
//         x.pop();
//     }
//     println!("{}", a);
// }

/*
    main
    ptr: a -> ptr: 0
              len: 8
              cap: 8
         b -> ptr: 15
              len: 8
              cap: 8
*/

/*
    | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
    |"A"|" "|"S"|"t"|"r"|"i"|"n"|"g"|
*/

/*
    global frame
    ptr main -> main()
*/
// fn main() {
//     let x = 10;
//     let y = x;
//     let z = x;

//     copy(true);
//     copy("a");
//     copy("a slice");
//     copy(x);
//     copy(String::from("Test"));
// }

// fn copy<T>(t: T) -> T
// where
//     T: Copy,
// {
//     let x = t;
//     x
// }

/*
    main
    x = 10
    y = 10
    z = 10
*/

/*
    main
    ptr: a -> ptr: 0
              len: 8
              cap: 8
    a.pop()
         a -> ptr: 0
              len: 7
              cap: 8
*/

/*
    | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
    |"A"|" "|"S"|"t"|"r"|"i"|"n"|"g"|
*/

// fn main() {
//     let x = 10;

//     let y = x;

//     print(x, y);
// }

// fn print(x: u8, y: u8) {
//     println!("{}, {}", y, x);
// }

/*
    ptr: main
    args:
    local vars: x = 1
        print(x)
    return value: ()
    drop x
*/

/*
    print
        a = 1 as u32
        println!("value {}", a)
    return value ()
    drop a
*/

/*
    println!()
    -> ()
*/

编译器针对一个代码分析:

fn main() {
    let x = &42;
}

Shell下输入如下命令:

$ rustc --edition 2018 --emit mir ex5.rs

可以得到如下mir中间代码:

// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn  main() -> () {
    let mut _0: ();                      // return place in scope 0 at ex5.rs:1:11: 1:11
    let _1: &i32;                        // in scope 0 at ex5.rs:2:9: 2:10
    let mut _2: &i32;                    // in scope 0 at ex5.rs:2:13: 2:16
    scope 1 {
        debug x => _1;                   // in scope 1 at ex5.rs:2:9: 2:10
    }

    bb0: {
        StorageLive(_1);                 // bb0[0]: scope 0 at ex5.rs:2:9: 2:10
        _2 = const main::promoted[0];    // bb0[1]: scope 0 at ex5.rs:2:13: 2:16
                                         // ty::Const
                                         // + ty: &i32
                                         // + val: Unevaluated(DefId(0:3 ~ ex5[317d]::main[0]), [], Some(promoted[0]))
                                         // mir::Constant
                                         // + span: ex5.rs:2:13: 2:16
                                         // + literal: Const { ty: &i32, val: Unevaluated(DefId(0:3 ~ ex5[317d]::main[0]), [], Some(promoted[0])) }
        _1 = _2;                         // bb0[2]: scope 0 at ex5.rs:2:13: 2:16
        StorageDead(_1);                 // bb0[3]: scope 0 at ex5.rs:3:1: 3:2
        return;                          // bb0[4]: scope 0 at ex5.rs:3:2: 3:2
    }
}

promoted[0] in  main: &i32 = {
    let mut _0: &i32;                    // return place in scope 0 at ex5.rs:2:13: 2:16
    let mut _1: i32;                     // in scope 0 at ex5.rs:2:14: 2:16
    scope 1 {
    }

    bb0: {
        _1 = const 42i32;                // bb0[0]: scope 0 at ex5.rs:2:14: 2:16
                                         // ty::Const
                                         // + ty: i32
                                         // + val: Value(Scalar(0x0000002a))
                                         // mir::Constant
                                         // + span: ex5.rs:2:14: 2:16
                                         // + literal: Const { ty: i32, val: Value(Scalar(0x0000002a)) }
        _0 = &_1;                        // bb0[1]: scope 0 at ex5.rs:2:13: 2:16
        return;                          // bb0[2]: scope 0 at ex5.rs:2:13: 2:16
    }
}

看下你写下main.rs这个代码:

let x = &42;

转化成 mir 代码部分:

let mut _0: &i32;  //  分配一个 <_0: LV> 类型是 <&i32>
let mut _1: i32;   //  分配一个 <_1: RV> 类型是 <i32>
_1 = const 42i32;  //  把 <const 42i32> 写入 <_1: RV> 中
_0 = &_1;          //  把 <_1: RV> 的地址写入 <_0: LV> 中