3 ms·
I describe it as intuition because people talk about these things intuitively. They say things like "the borrow checker finally accepts code I know it should ha
by steveklabnik 3y ago
I describe it as intuition because people talk about these things intuitively. They say things like "the borrow checker finally accepts code I know it should have accepted all along". For example, let's look at the example used in the very first part of the NLL RFC:
fn capitalize(data: &mut [char]) {
todo!()
}
fn foo() {
let mut data = vec!['a', 'b', 'c'];
capitalize(&mut data[..]);
data.push('d');
}
This code works just fine. But maybe we want to pull the slice out into a variable. So we do this:
fn foo() {
let mut data = vec!['a', 'b', 'c'];
let slice = &mut data[..];
capitalize(slice);
data.push('d');
}
This code would fail to compile under the old borrow checker. Why? Because you have two &mut references pointing to the same thing. People are, I think, rightfully surprised that this code doesn't just work. It is very unintuitive that extracting a temporary to a variable causes code to no longer work. But that is exactly it: you are now changing the scope from small to large, thanks to lexical scoping.
But people didn't talk about this code as like "Oh I understand that I am introducing a lexical scope and that means it conflicts." They talk about it like "but I don't use slice anywhere after the capitalize call. This code is safe. There's no point where these two mutable references are active at the same time. And you know I'm right because the temporary version works just fine, and this is equivalent." That is, their perception of scope maps closer to a CFG than to lexical rules.
Frankly, I am not even sure that many Rust programmers could describe what a control flow graph is, let alone that they somehow learn the exact rules and then apply them. People don't learn programming languages that way, just like they don't learn human languages that way. It is largely a practice of building up intuitions and testing them out.
- rayiner 3y ago> Frankly, I am not even sure that many Rust programmers could describe what a control flow graph is, let alone that they somehow learn the exact rules and then apply them. People don't learn programming languages that way, just like they don't learn human languages that way. It is largely a practice of building up intuitions and testing them out. I think a lot of programmers want to be able to understand how the language works in terms of formal rules and predict whether a given piece of code will pass the compiler without guess-and-check. Indeed, it seems like a pervasive complaint about Rust is that it requires too much of this “try things until it compiles” approach, and makes it hard to develop a clear mental model of how the borrow checker works.
- zaptheimpaler 3y agoAs a novice Rust programmer I agree with everything here lol. I don't know what a CFG is, and the 2nd code block not compiling breaks referential transparency which is very very weird to me. If i understand correctly, the lexical scope idea is just saying that `slice` exists in scope until the end of the block regardless of what happens inside the block, whereas CFG is smarter and says that since we don't use slice after line 3, its not in scope anymore. So from my POV the non lexical scope is unequivocally better, more fine-grained reasoning from the type system.