在学习一门新的语言中,我写下 2023 年的新目标:学习 RUST 语言。这几天我把 RUST 语法过了一遍。
有了其它编程语言的基础,RUST 语法学起来不难。但 RUST 毕竟是一门全新设计的语言,如果和现有语言完全一样,那就失去了存在的价值。RUST 作为一门年轻的语言,博采众长,在设计上进行了取舍,所以有很多独特之处。今天我就来聊一聊 RUST 语言中的变量。
所有的编程语言都有变量,RUST 语言在设计上独特地方在于:
- Rust中的变量默认是不可变的。
- 一个新声明的变量可以覆盖掉旧的同名变量。
如果仅仅是学习语言,这两点特性掌握起来也很简单,在这里我想探讨的是背后的设计理念。
不变量与常量
首先,需要澄清一下,不可变变量并不是常量,RUST 语言提供了常量(关键词 const,这一点和 Java 有所不同)。
在 RUST 中,常量不仅是默认不可变的,它还总是不可变的,而变量虽然默认不可变,但可以添加关键字 mut 使其可变。
常量的值必须在编译器就能确定,所以你无法将一个函数的返回值,或其他需要在运行时计算的值绑定到常量上,这一点其它的编程语言也有这种要求,不可变量的值可以运行时确定。
此外,常量在整个程序运行的过程中都在自己声明的作用域内有效,这使得常量可以被用于在程序的不同代码之间共享值,这个在后面探讨作用域时再进一步分析。
为什么变量默认不可变
变量不可变,在其它语言上也有这样的设计。比如 Java 语言,变量前加上 final 修饰词,在 C 中,const 可用来修饰指针变量、函数参数、函数返回值、成员变量、。。。表明这些变量不会被修改。但在这些语言中,变量默认是可以修改,RUST 默认变量不可变,这背后的考量是什么?
我觉得这背后的设计理念就是人总是懒惰的,会倾向于使用更简短的写法。就拿 C 语言来说吧,很多时候函数传递指针或引用时,不修改指针或者引用的值,仅仅是为了减少对象拷贝的开销,这个时候应该在前面加上 const 修饰词,但很多程序员不会加。不信你可以看看自己写的 C 代码,有没有给不修改成员变量的成员函数加上 const 修饰符。
所以默认不可变的第一个好处,是提高了不可变量的使用频次。let a也能用,let mut a,也能用,那么大部分时候,大家会省去mut。
默认不可变的另一个好处是,简化逻辑。在多线程编程中,最大的麻烦是数据竞争,假如语法层面保证了一个值的不可变,就不需要锁保护(当然这只是一方面)。
变量可以被覆盖(隐藏),这是什么逻辑
看看如下代码,就可以明白什么叫变量覆盖:
代码语言:javascript复制 let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse()
.expect("Please type a number!");
println!("You guessed: {}", guess);
}
这段代码中,guess 变量先是 String 类型,接着又改变其类型为 u32。这在我所学过的语言中都是不被允许的。这背后的逻辑是什么?
我认为还是为了精简代码。我们是否也经常碰到为了取变量的不同类型值,为变量名取 xxx_str 之类的不同的名字。比如坐标计算中,有的使用浮点型、有的使用整型,为类型转化中间变量取名就很头疼。有了 RUST 这种语言特性,不需要为了一些中间变量取一些特别的名字。
在 C 中,如果我们写了多个循环语句,可能需要使用上 i, j, k 之类的循环变量,在 RUST 语言中一个 i 变量就可以。
当然,变量改变了类型可能会引起程序员的困惑,但由于编译器的强大,如果在代码中使用了错误的变量类型,在编译期间就可以检查出来。而变量类型定义和使用变量的代码离得更近,逻辑上更清晰,更不容易出错。
当然,关于 RUST 语言的语法还有很多可以探讨的,但大多数只是和其它语言在定义上有所不同,理念上一致,不需要特别讨论。但 RUST 语言中的所有权是一个非常独特的设计,这个值得重点说一说,下一篇文章中,我将探索 RUST 语言中的 所有权,敬请关注。