4 ms·
Setting aside any terminology for a second, consider this rust program: fn main() { let x = 1; let capture = || x; let x = 2; println!("{}", capt
by dilap 7y ago
Setting aside any terminology for a second, consider this rust program:
fn main() {
let x = 1;
let capture = || x;
let x = 2;
println!("{}", capture());
println!("{}", x)
}
This will print 1 and then 2, whereas python would print 2 and 2.
Hence, you can see that the formulation "let mut" is equivalent to python, not "let" followed by "let".
Here's the rust program that prints 2 and 2:
fn main() {
let mut x = 1;
let ptr = &x as *const i32;
let capture = || unsafe{ *ptr };
x = 2;
println!("{}", capture());
println!("{}", x);
}
(I had to use unsafe otherwise the borrow checker will complain will I modify x from underneath the closure; maybe a more elegant way to make the same point -- I don't really know rust...)
- joshuamorton 7y agoActually hmm, I may want to take back my earlier comment. There are multiple things at play. There's scoping (where rust will copy across scope boundaries for non-ref types, which allows closing over something as in your first example above). Then there's mutable refs and mutable variables, which as hope-striker mentioned I was confusing, possibly because I was using ints in my example. If instead we used a vec: fn main() { let x = vec![0,1,2] x.push(3) // fails since x isn't mutable } There's no clear direct related concept here by default. If we're allowed to use pytype, you get this: def main(): x: Sequence[int] = [1,2,3] # Sequences aren't mutable x.push(3) # fails since x isn't mutable Cool, so mutable and immutable values are possible in both langs. What about refs? Well we went through that one, if you pass a mutable ref to a function in rust, you can modify the ref in ways that just aren't possible in python: fn main() { println!("Hello, world!"); let mut x: i32 = 3; modifies(&mut x); println!("{}", x); } fn modifies(x: &mut i32) { *x = 5; } There's nothing analogous to this in python. Everything is always passed as a mutable "value"[1], nothing is passed as a ref. Cool so that's mutable variables and mutable references. That leaves this weird scoping issue. In rust (and in cpp) there's lots of scopes. Any set of braces creates a new scope, and so shadowing can happen across scopes. Lambda capture/closure happens over the scope. A given scope binds a name to a value, or a set of names to their values. Python's a bit different, only new names are created in the scope. If a name isn't accessible in the given scope, the name is pulled from parent scopes etc. So for the capturing behavior you want, there's weird nonlocal stuff that needs to be done, or you can explicitly make an additional scope, which removes the wonky behavior. If the name were really mutable, you'd be able to change what x referred to in the enclosing scope, which you can't. tl;dr: This isn't mutable names, its python's (admittedly abnormal) scoping rules. [1]: Unless you add in mypy or whatnot, where the typechecker will prevent you from modifying something that is non-mutable, but unlike in rust this isn't done with mutability as a first class citizen, its just that some interfaces expose mutating methods (`append`) and some don't. You can pass a list to a function that expects a list or a sequence, and the first case is mutable, while the second isn't.
- dilap 7y agoPython's scope & mutability rules are idiosyncratic, but that's a distraction from what's going on here. Let's go back to steveklabnik's ancestor comment: "That's not an identical translation, the identical Rust would be:" fn main() { let mut x = 1; x = "foo"; } He was saying the identical Rust would not be: fn main() { let x = 1; let x = "foo"; } These are being compared to the following Python: x = 1 x = "foo" So consider these slightly enhanced versions of the fundamental question posed above. Python: def mystery_py(): x = 1 capture = lambda: x x = 2 return x * capture() Rust: fn mystery_a() -> i32 { let x = 1; let ptr = &x as *const i32; let capture = || unsafe{ *ptr }; let x = 2; return x * capture(); } fn mystery_b() -> i32 { let mut x = 1; let ptr = &x as *const i32; let capture = || unsafe{ *ptr }; x = 2; return x * capture(); } If you compare return values, you will find that mystery_py() returns the same as mystery_b(). So! I think you must agree that steveklabnik was right -- the rust code that is equivalent to the python code is the "let mut" variant. (Because surely you would not argue code that returns a different value is equivalent?!) So now the question is, why? Rather than answer, I will trollishly pose 2.5 more questions: What would an implementation of mystery_a and mystery_b look like in scheme? Would it be possible to author mystery_b in scheme if your "!" key was broken? (How about in some other purely functional language?)
- joshuamorton 7y agoI disagree that those are doing the same thing. I propose that the actual answer is c: use std::cell::RefCell; fn mystery_c() -> i32 { let x = RefCell::new(1); let capture = || x.borrow(); x.replace(2); return *x.borrow() * *capture(); } fn main() { println!("{}", mystery_c()) } Which is what I meant when I said that Box<T> might be the analogous thing (I guess it's actually RefCell, whoops!). And note that in this case, x is immutable :P That said I accept your broader point, the effect is that python names act like mutable rust names, although the reality is slightly more complex (my final example is, I believe, the closest to actual reality).
- 7y ago