5 ms·
By default new types in Rust are not Copy, so when you pass values of these types around they are moved, not copied. It leads for example to freedom to do somet
by ordu 4y ago
By default new types in Rust are not Copy, so when you pass values of these types around they are moved, not copied. It leads for example to freedom to do something like:
fn move_things(a: SomePrettyBigType) -> EvenBiggerType
When I was taught programming, I learnt that it is a bad thing, because passing big values to functions needs a lot of copying and is very slow. Not so with Rust. I personally did all the checks with emit=asm, and noticed how rustc passes an argument's address to a function (instead of copying an argument value) and one more address of a place, where function can build a value of EvenBiggerType.
I never checked whether rustc preallocate memory for uninitialized variable or not. I bet it doesn't even in debug builds, because it knows in advance when variable is used. C compiler can work without this knowledge, but rustc cannot: it needs to check lifetimes and borrowing. Yes, it is normally seen as an optimization in C, but in rust many of C optimizations become a part of a language.
> Do you have some sources?
No, sorry, I have no source, my mental representation of rust code is a creation of my mind (being modest I'd add, that it is a second-hand creation probably, I'm not invented Rust, so someone thought of it before me, and I've read a lot what others think, probably I picked it up somewhere). But I can give you a link[1] to an article comparing C++ and Rust move semantics. There you can see the difference between "variable has no value" and "variable have a value that means 'moved out'". I see this difference as static vs dynamic tracking of "moved out" status (probably the article talks about it, I didn't reread it now and do not remember clearly). And I interpret it as a difference in a variable semantic: rust's variable has no memory, rust's values have. C++ variable on the contrary has a memory which is used to store value of the variable.
I see it as a semantic differences of variables in Rust, because this semantics works. I can predict machine code generated, I can predict when borrow checker will become unhappy, I can track lifetimes in my mind. I mean a scope of a variable and a lifetime of a value are different things. I cannot imagine how anyone can think of a lifetime of a value without believing that memory occupied by a value is an attribute of a value. And at my first months of dealing with Rust I really tried to find a way.
[1] https://www.thecodedmessage.com/posts/cpp-move/ https://www.thecodedmessage.com/posts/cpp-move/
- ithkuil 4y agoIn rust moves are just copies that guarantee that no outstanding references to the old location can be held anywhere and thus the compiler is allowed to optimize away the copy. The same thing can be done by C/C++ compilers under some circumstances.
- Dylan16807 4y ago> I never checked whether rustc preallocate memory for uninitialized variable or not. I bet it doesn't even in debug builds, because it knows in advance when variable is used. C compiler can work without this knowledge, but rustc cannot: it needs to check lifetimes and borrowing. Yes, it is normally seen as an optimization in C, but in rust many of C optimizations become a part of a language. Local variables just get a slot on the stack. Allocation is done for several at once, and it would be a lot of work to handle it per-variable for negligible benefit. It would be especially difficult to handle code that has multiple variables and moves one into a Box, chosen by a calculation, then keeps using the rest. I bet none of that memory is going to get reclaimed, it will just sit there marked "do not touch". But that's all about a stack with optimizations and deliberately vague semantics. We can also learn something by looking at objects. Rust objects work like C++. To move a field out, you have to replace it with a placeholder version of that field.
- ordu 4y agoIt seems to me that you believe, that there is some canonical representation of a source code by a machine code, and that optimizations are out of it. I do not think it this way. As I see it, compiler gets source code and generates machine code that have the same side-effects as I asked. Compiler is free to use any way to do it, while it doesn't change side-effects. I choose to think this way because real generated machine code are more like this. Maybe debug builds are close to an idea of a canonical machine code, but I do not know, because I see no reason to look at machine code of debug builds. Moreover code without optimizations applied is harder to read with all that noise of unnecessary actions. > Local variables just get a slot on the stack. Allocation is done for several at once, and it would be a lot of work to handle it per-variable for negligible benefit. Local variables often can be not allocated at all. They can be eliminated by a compiler. I hold a view that it is incorrect to talk about variables at all if we talk about generated machine code. It is just like with a handcrafted assembler code: a counter can be pushed on the stack, then popped, increased, used as an index into an array, then pushed back to be popped once more to check should we do one more iteration of this. It is incorrect to say that counter is on the stack or it is in a register because sometimes it is here and sometimes it is there. The counter exists only in my mind, not in the code. When another person can deduce from my code the existence of the counter it is not because the counter exists in the code, it is because another person can deduce what I was trying to do. Look at this: let s = SomeBigStruct::new(); return Box::new(s); With any luck s can be eliminated, value returned by SomeBigStruct::new() will not be stored on the stack at any point. And it is not the thing we can ignore, because SomeBigStruct can be very large and it may be not a good idea to place it on the stack or to spend time copying it from the stack into a Box. Rust allow us to eliminate variably ourselves: return Box::new(SomeBigStruct::new()); but it is the same basically: SomeBigStruct::new returns a big value which is then moved into a Box. If rustc was unable to move this value from SomeBigStruct::new into a Box without a call to memcpy, then Rust would be an academical curiosity, not a practical language at all. But sometimes I need to store SomeBigStruct in an intermediate variable for some reason, and Rust must be able to avoid memcpying nevertheless. > It would be especially difficult to handle code that has multiple variables and moves one into a Box, chosen by a calculation, then keeps using the rest. Yeah, there are different situations and different situations need different methods. > But that's all about a stack with optimizations and deliberately vague semantics. We can also learn something by looking at objects. Objects are objects. They are not variables, they are values. Structure field name is not a variable, it is a way to deal with a value of a variable. When I create a struct or an array, I allocate memory for that value, not for a variable.