3 ms·
I have mainly read about rust, not worked with it, but the section about: let x = compute_x(); f(x.clone()); g(x); was confusing/surprising to me.
by topaz0 4y ago
I have mainly read about rust, not worked with it, but the section about:
let x = compute_x();
f(x.clone());
g(x);
was confusing/surprising to me. Isn't the whole point of default-immutable that you can use x without owning a copy? Forgive me if this is a stupid question.
- hawski 4y agoI'm no Rust expert, but it would be ok if you would do borrows there. let x = compute_x(); f(&x); g(&x); Otherwise you have moves there. Now it depends on the API, it may want the value and not the reference (a borrow).
- deleted 4y ago[deleted]
- topaz0 4y agoI see. Is it not common practice for functions to expect references when they can (i.e. when they don't need to modify the argument)?
- tialaramex 4y agoNot just when they don't need to modify the argument, when they don't need to consume it. The way these parameters are written takes ownership of x, it's gone, not just modified. For a non-trivial object (which apparently x is), you would always prefer to write &x (immutable reference to x) or at least &mut x (mutable reference, not applicable here because x is not defined as a mutable thing). So apparently these functions intend to consume x. That's not common but there are a reasonable number of Rust APIs which consume larger things on purpose.
- topaz0 4y ago> So apparently these functions intend to consume x. I guess in that case I don't really get the complaint. If there's a good reason for f or g to consume x, I would think I would always want the copy to be explicit. But I could still be missing some subtlety I guess.
- tialaramex 4y agoYeah, it feels reasonable to me, but I'm a happy Rust programmer, perhaps if you're mostly working in functional languages this feels very wrong, I would not know.
- steveklabnik 4y agoRust can be like this, because it tends to expose a lot of information, and not everyone cares about every type of information.
- ismailmaj 4y agoThe common scenario for f and g to consume x is that x is a smart pointer type e.g. Rc/Arc. to make it clear that the clone is "cheap", some Rust developer prefer using `Arc::Clone(&x)` over `x.clone()`.
- tialaramex 4y agoLet's consider a couple of scenarios since we don't know x's type here presumably on purpose 1. Suppose x is a Teapot, some complicated object with lots of internal state - it has about two cups of Early Grey in it, and is a lovely blue and white pattern. If we call f(x) then we're giving our Teapot, x, to this f() function. It apparently doesn't give anything back, so the Teapot is gone now! In Rust we say x was moved into f. Maybe f empties the tea out of the teapot, then smashes it and uses the broken pieces to fill a pothole in the road. Weird. So we use the clone() call to ask for an exact duplicate of the Teapot, now f(x.clone()) only gives the clone to this function f, our original Teapot remains unaltered in our possession. Because we've still got x, we can call g() which apparently like f also wants the actual Teapot, maybe this function plants a cactus in it and puts it on a high shelf. Once again our Teapot, x, is gone because we gave it to g(). Now, most Rust functions on complicated objects like a Teapot (or more realistically, a Vec or a String or File) don't move parameters like this, they want either a mutable reference which you'd write &mut x or an immutable reference, which is written &x. If these functions f() and g() only needed immutable references, our Teapot x is fine. We can't use mutable references because x itself is not labelled as mutable, if it was, the Teapot might be altered, but it's still a Teapot, the functions don't own it, they were just given a reference and so they aren't allowed to destroy it for example. OK 2. Suppose x is just a 32-bit unsigned integer, u32 in Rust Now things are different, because the u32 type implements Copy, a trait which tells the Rust compiler, "My value is literally the value of the bytes in RAM, if you copy those bytes, that's all I am, that's a copy of me". So now even though f(x) moves x into f, the compiler knows x is just this 4 byte value, it can give f the same 4 byte value and keep the one in x, and everybody is happy. f() can do whatever it wants with that value, x is unaffected. We can call x.clone() if we want, but it just makes another identical 4 byte value, kinda pointless. Then g(x) does the same, even if it wants to "move" the parameter, the compiler just gives it a copy anyway, no harm done. Does this help?
- topaz0 4y agoIt all makes sense, sure. It's not that I don't understand why rust would distinguish these things. The question is really, do people writing rust code not mostly default to passing the immutable reference when that's an option (i.e. when the function doesn't need to modify the input). And why not? (Edit: just saw your other comment on the other thread, will respond there)