<-- Home |--rust |--rfe

RFE-002: 工程师(粗)Rust入门之变量进阶

软件开发常态

小龙,程序编得如何?为师的跑得第二快已经肝到修仙199差点脱离路边野狗的水准,你不会SGP4还没编好吧,我知道那肯定是因为需求不明确/初期理解有误/不可预见的技术障碍/不可抗力影响的软件开发延期/不能预见的服务器宕机拖慢开发进度/压缩文件校验错文件不能打开/开发机器不兼容无法现场演示,懂的,懂的,你就继续拖吧……

那我们先把三角形的边长计算结果看一下吧。

我们说好的算个边长3.0/4.0,角度从30°到150°,30°递增。

小龙红温了脸,嘴里说了一些我不想听见的话,啪啪按键盘。毕竟是个硕士,还是有些长处,马上给我看到了结果。

三角形计算(续)

 1fn main() {
 2    let deg2rad = std::f64::consts::PI / 180.0;
 3
 4    println!("{:>10}{:>10}{:>10}{:>10}", "a", "b", "α", "c");
 5
 6    let a: f64 = 3.0;
 7    let b: f64 = 4.0;
 8    let angle: f64 = deg2rad * 30.0;
 9    let c_squared = a.powi(2) + b.powi(2) - 2.0 * a * b * angle.cos();
10    let c = c_squared.sqrt();
11    println!("{:>10.4}{:>10.4}{:>10.4}{:>10.4}", a, b, angle / deg2rad, c);
12
13    let a: f64 = 3.0;
14    let b: f64 = 4.0;
15    let angle: f64 = deg2rad * 60.0;
16    let c_squared = a.powi(2) + b.powi(2) - 2.0 * a * b * angle.cos();
17    let c = c_squared.sqrt();
18    println!("{:>10.4}{:>10.4}{:>10.4}{:>10.4}", a, b, angle / deg2rad, c);
19
20    let a: f64 = 3.0;
21    let b: f64 = 4.0;
22    let angle: f64 = deg2rad * 90.0;
23    let c_squared = a.powi(2) + b.powi(2) - 2.0 * a * b * angle.cos();
24    let c = c_squared.sqrt();
25    println!("{:>10.4}{:>10.4}{:>10.4}{:>10.4}", a, b, angle / deg2rad, c);
26
27    let a: f64 = 3.0;
28    let b: f64 = 4.0;
29    let angle: f64 = deg2rad * 120.0;
30    let c_squared = a.powi(2) + b.powi(2) - 2.0 * a * b * angle.cos();
31    let c = c_squared.sqrt();
32    println!("{:>10.4}{:>10.4}{:>10.4}{:>10.4}", a, b, angle / deg2rad, c);
33
34    let a: f64 = 3.0;
35    let b: f64 = 4.0;
36    let angle: f64 = deg2rad * 150.0;
37    let c_squared = a.powi(2) + b.powi(2) - 2.0 * a * b * angle.cos();
38    let c = c_squared.sqrt();
39    println!("{:>10.4}{:>10.4}{:>10.4}{:>10.4}", a, b, angle / deg2rad, c);
40}

这小代码,写得那叫一个整齐。运行结果,符合强迫症孩子的审美。

1 >rustc .\triangles.rs ; .\triangles
2         a         b         α         c
3    3.0000    4.0000   30.0000    2.0531
4    3.0000    4.0000   60.0000    3.6056
5    3.0000    4.0000   90.0000    5.0000
6    3.0000    4.0000  120.0000    6.0828
7    3.0000    4.0000  150.0000    6.7664

这也是为啥小龙那么噎人我还是挺喜欢这个徒弟的。

命令式程序设计

这个程序,就是典型的命令式程序设计……就像是在命令行终端一条一条敲命令,计算机按照给出的指示完成计算,输出结果。

Fortran程序员一般就是这么用的。

但是这个rust程序中还是有两点有意义的东西。

  1. 每个块中的变量名称是相同的!Rust并不对变量名重新定义报警。
  2. 同名的不可变变量设定不同的值,计算结果是正确的。

这对我这种C/C++程序员来说可就太有点看不惯了。这玩意,到底是变量,还是变量。

变量

众所周知,Rust的默认变量位于栈内存中,这要求知道变量的尺寸Sized。分配在栈内存的变量分配速度非常快,访问非常有序。那我们这一个劲的let angle =下去,是产生了很多个栈内存变量?还是一个栈内存地址对应的值不停在改变?

是时候给小龙露一手,不然他对师傅的尊重可是越来越稀缺了……

 1use std::any::type_name_of_val;
 2use std::mem::size_of_val;
 3use std::ptr::addr_of;
 4
 5fn main() {
 6    let a = 5;
 7    let a5_addr = (&a) as *const i32 as usize;
 8    println!(
 9        "a{}  {}  {:p}  {}",
10        a,
11        type_name_of_val(&a),
12        &a,
13        size_of_val(&a)
14    );
15
16    let a = 6_i64;
17    let a6_addr = (&a) as *const i64 as usize;
18    println!(
19        "a{}  {}  {:p}  {}",
20        a,
21        type_name_of_val(&a),
22        &a,
23        size_of_val(&a)
24    );
25
26    let a = 7.0_f32;
27    let a7_addr = (&a) as *const f32 as usize;
28    println!(
29        "a{}  {}  {:p}  {}",
30        a,
31        type_name_of_val(&a),
32        &a,
33        size_of_val(&a)
34    );
35
36    let a = 8.0;
37    let a8_addr = (&a) as *const f64 as usize;
38    println!(
39        "a{}  {}  {:p}  {}",
40        a,
41        type_name_of_val(&a),
42        &a,
43        size_of_val(&a)
44    );
45
46    // show the value of addresses again
47    println!("{:?} {:#x} {}", addr_of!(a5_addr), a5_addr, unsafe {
48        *(a5_addr as *const i32)
49    });
50    println!("{:?} {:#x} {}", addr_of!(a6_addr), a6_addr, unsafe {
51        *(a6_addr as *const i32)
52    });
53    println!("{:?} {:#x} {}", addr_of!(a7_addr), a7_addr, unsafe {
54        *(a7_addr as *const f32)
55    });
56    println!("{:?} {:#x} {}", addr_of!(a8_addr), a8_addr, unsafe {
57        *(a8_addr as *const f64)
58    });
59}

这个运行结果就很清楚了。

1>.\variables
2a5  i32  0x30a98ff13c  4
3a6  i64  0x30a98ff238  8
4a7  f32  0x30a98ff33c  4
5a8  f64  0x30a98ff438  8
60x30a98ff140 0x30a98ff13c 5
70x30a98ff240 0x30a98ff238 6
80x30a98ff340 0x30a98ff33c 7
90x30a98ff440 0x30a98ff438 8

首先,栈内存中同名不可变变量(多次声明),产生了不同的地址,其内存大小都根据数据类型来分配。

  • i32,32 bit, 4 bytes
  • i64, 64 bit, 8 bytes
  • f32,32 bit, 4 bytes
  • f64, 64 bit, 8 bytes

可以看到,随着变量申请let语句(命令)一条一条下来,地址有序递增……(不知道是个什么序,但是也不能在小龙面前露怯)。而且,据说,4字节的变量地址可以被四整除,8字节变量地址可以被8整除,叫做什么对齐颗粒度(啊……呸)

最好玩的是,这个地址存在来,可以通过unsafe来继续访问,整个过程中,虽然a被移动到指向其它内存地址,前面的内存地址所保存的值依然没有改变(通过unsafe { *(a5_addr as*const i32) }访问的)。这里通过as把地址usize转化为指针。这是不是传说中的No Drop(我记不认识这两个单词也不知道意思)。

对于工程师所需要的简单计算应用,能够在栈内存中用这种简单的方式解决的,实际上Rust的这个变量名重用还是挺合理的……

可变 vs. 不可变

对于工程师而言,代码增加(重复代码)说不定还是好事情呢……本来我们的计算代码大部分都是一次性代码,就跟计算器一样,按了结果记下来没记下来都没了,或者跟草稿纸一样,写完就碎了……

Rust的不可变变量或者说变量默认不变,其实对于工程师也是好事情。工程师所写的每一个数字,其实都要对应显示中的一个实际的东西的实际的数据。如果是显示中另外一个实际东西的实际数据,本来就跟前面按个是不一样的。比如A设备的长度和B设备的长度,这是两个东西,但是都可以叫做长度。所以说Rust的这个鬼玩意,难道是针对我们这种大老粗专门设计的?

一般研究人员理解的长度变化,优化设计,对我们工程师而言,就是设计20台不同长度的玩意,选一个最好的。你小子没钱,做三台。可不是这样的吗……哪有什么变化的变量,那个长度的变化只会因为温度和测量不准确性导致我们得到的数据不准确!那个长度就是那个设备的长度,这是一个固定(有条件)不变(在测量能力范围内)的量。

所以说,Rust太适合工程师了……

至于说,什么爆栈啊,什么重复代码软件工程学啊(软件哪也叫工程?),这就看小龙的手速,跟我老高工有什么关系……


文章标签

|-->rust |-->variables |-->tutorial |-->engineering |-->


GitHub