Rust 的变量定义有一个比其它语言更奇怪的地方,它可以让你在同一个作用域里面“重复绑定”同一个名字,甚至可以把它绑定到另外一个类型:
let mut x: i32 = 1; x = 7; let x = x; // 这两个 x 是两个不同的变量 let y = 4; // 30 lines of code ... let y = "I can also be bound to text!"; // 30 lines of code ... println!("y is {}", y); // 定义在第二个 let y 的地方 在 Yin 语言最初的设计里面,我也是允许这样的重复绑定的。第一个 y 和 第二个 y 是两个不同的变量,只不过它们碰巧叫同一个名字而已。你甚至可以在同一行出现两个 x,而它们其实是不同的变量!这难道不是一个很酷,很灵活,其他语言都没有的设计吗?后来我发现,虽然这实现起来没什么难度,可是这样做不但没有带来更大的方便性,反而可能引起程序的混淆不清。在同一个作用域里面,给两个不同的变量起同一个名字,这有什么用处呢?自找麻烦而已。
比如上面的例子,在下面我们看到一个对变量 y 的引用,它是在哪里定义的呢?你需要在头脑中对程序进行“数据流分析”,才能找到它定义的位置。从上面读起,我们看到 let y = 4,然而这不一定是正确的定义,因为 y 可以被重新绑定,所以我们必须继续往下看。30 行代码之后,我们看到了第二个对 y 的绑定,可是我们仍然不能确定。继续往下扫,30行代码之后我们到了引用 y 的地方,没有再看到其它对 y 的绑定,所以我们才能确信第二个 let 是 y 的定义位置,它是一个字符串。
这难道不是很费事吗?更糟的是,这种人工扫描不是一次性的工作,每次看到这个变量,你都要疑惑一下它是什么东西,因为它可以被重新绑定,你必须重新确定一下它的定义。如果语言不允许在同一个作用域里面重复绑定同一个名字,你就根本不需要担心这个事情了。你只需要在作用域里面找到唯一的那个 let y = ...,那就是它的定义。
也许你会说,只有当有人滥用这个特性的时候,才会导致问题。然而语言设计的问题往往就在于,一旦你允许某种奇葩的用法,就一定会有人自作聪明去用。因为你无法确信别人是否会那样做,所以你随时都得提高警惕,而不能放松下心情来。
let mut x: i32 = 1; x = 7; let x = x; // 这两个 x 是两个不同的变量 let y = 4; // 30 lines of code ... let y = "I can also be bound to text!"; // 30 lines of code ... println!("y is {}", y); // 定义在第二个 let y 的地方 在 Yin 语言最初的设计里面,我也是允许这样的重复绑定的。第一个 y 和 第二个 y 是两个不同的变量,只不过它们碰巧叫同一个名字而已。你甚至可以在同一行出现两个 x,而它们其实是不同的变量!这难道不是一个很酷,很灵活,其他语言都没有的设计吗?后来我发现,虽然这实现起来没什么难度,可是这样做不但没有带来更大的方便性,反而可能引起程序的混淆不清。在同一个作用域里面,给两个不同的变量起同一个名字,这有什么用处呢?自找麻烦而已。
比如上面的例子,在下面我们看到一个对变量 y 的引用,它是在哪里定义的呢?你需要在头脑中对程序进行“数据流分析”,才能找到它定义的位置。从上面读起,我们看到 let y = 4,然而这不一定是正确的定义,因为 y 可以被重新绑定,所以我们必须继续往下看。30 行代码之后,我们看到了第二个对 y 的绑定,可是我们仍然不能确定。继续往下扫,30行代码之后我们到了引用 y 的地方,没有再看到其它对 y 的绑定,所以我们才能确信第二个 let 是 y 的定义位置,它是一个字符串。
这难道不是很费事吗?更糟的是,这种人工扫描不是一次性的工作,每次看到这个变量,你都要疑惑一下它是什么东西,因为它可以被重新绑定,你必须重新确定一下它的定义。如果语言不允许在同一个作用域里面重复绑定同一个名字,你就根本不需要担心这个事情了。你只需要在作用域里面找到唯一的那个 let y = ...,那就是它的定义。
也许你会说,只有当有人滥用这个特性的时候,才会导致问题。然而语言设计的问题往往就在于,一旦你允许某种奇葩的用法,就一定会有人自作聪明去用。因为你无法确信别人是否会那样做,所以你随时都得提高警惕,而不能放松下心情来。