5 ms·
Python'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: "Th
by dilap 7y ago
Python'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).
- dilap 7y agoYou lost me, boss! Why do you think mystery_c is closer to mystery_py than mystery_b?
- joshuamorton 7y agoLet's go on a journey. The answer is that I started with a hunch. You're treating x as a pointer sometimes, and a value other times. That seems strange, and unlike the python. In python the thing is always access the same way, it isn't a ptr type sometimes and a value type others. So first let's talk about scopes. In python, you aren't introducing a closure. If we do introduce a closure, like with an IIFE: def mystery_closure(): x = 1 closure = (lambda v: lambda: v)(x) x = 2 return x * closure() suddenly we get 2. The IIFE/outer closure here is equivalent to the capture happening in rust. So this is more equivalent to the rust examples than your python example. Closures are what matter, not variable mutability. Cool, so now let's add another wrinkle: `i32` in rust isn't a mutable type, there are no mutating methods on an i32. What happens if we use a type that has mutating methods, like a vec? Let's start in python, since python doesn't allow multiline lambdas, we have to swap to using an inner function, which is fine, this makes the structure a bit clearer in python. def mystery_ mutable(): x = [1] def closure(): def inner(v): v.append(2) return v return inner(x) x.append(3) return x + closure() And what if we do the same in rust? Well, we have to mark x as a mutable ref: fn mystery_b() -> Vec<i32> { let mut x = vec![1]; let ptr = &mut x as *mut Vec<i32>; let capture = || unsafe{ (*ptr).push(2); ptr }; x.push(3); unsafe { x.extend(capture().as_ref().unwrap().iter()); } return x } So the python value is a mutable ref, right? Well no, we're back to the whole issue of the closure being able to modify things outside itself in rust with a mut ref that we can't do with python: def mystery_mutable(): x = [1] def closure(): def inner(v): v = [5] v.append(2) return v return inner(x) x.append(3) return x + closure() This returns [1,3,5,2] in python. If you translate it to rust with a mutable ref pattern, you'll get [5,2,5,2] and the 3 will just disappear: fn mystery_mutable() -> Vec<i32> { let mut x = vec![1]; let ptr = &mut x as *mut Vec<i32>; let capture = || unsafe{ (*ptr) = vec![5,2]; ptr }; x.push(3); unsafe { x.extend(capture().as_ref().unwrap().iter()); } return x } So in python, the thing isn't a const ref, but it's not a mutable ref, either, and it's certainly not a value type. In languages like rust and cpp we describe calls as pass by reference or pass by value. Pass by value is mostly irrelevant here. When passing by reference, you can use a mutable or immutable reference. Immutable references don't allow you to modify the object, just read it. Mutable references allow you to modify or replace the object. With normal pointers and references, if you're able to modify the referenced object you can also replace it with an entirely new object. The reasons for this are tricky, but have to do with self references in methods (self/this has to be mutable for a mutable method to work). In rust and cpp the self reference is exposed, so you can make it point elsewhere. In python you can't do this. This means that its tricky to pass an immutable reference to a mutable object in rust/cpp, but in python this is the only way things get passed around. Rust calls this "interior mutability", and RefCell is the way to do interior mutability with references, as opposed to copyable types. The docs for RefCell actually call out passing &self to a method that requires mutability[1] as a use for RefCell, so in general you could use the RefCell to implement a python-like set of containers that can be passed "immutably" and still modified internally. In Pseudo-rust: struct PyVec { backing_arr: RefCell<Vec<T>> } impl PyVec { fn push(&self, v: T) { // This isn't mutable?! backing_arr.borrow_mut().push(v); } ... } Which would match python's semantics very closely [1]: https://doc.rust-lang.org/beta/std/cell/index.html#implementation-details-of-logically-immutable-methods https://doc.rust-lang.org/beta/std/cell/index.html#implement...