4 ms·
This is incorrect. It's trivial and compiles just fine. The argument here is that maybe for reasons the programmer doesn't want to - such as not wanting the buf
by dgacmu 2y ago
This is incorrect. It's trivial and compiles just fine. The argument here is that maybe for reasons the programmer doesn't want to - such as not wanting the buffer to outlive its use inside the loop, and they don't want to have to double-nest:
{
let mut buf = [0; 4096];
loop {
...
}
}
That accomplishes exactly the same goal but there's an argument -- not well made in the blog post -- that the compiler should be able to do some form of this hoisting automatically. In C, it would be automatic, because C doesn't make a zero-initialized promise for stack-allocated variables. In Rust it's not because the array is specified as zero-initialized. Of course, C's behavior comes with certain drawbacks of its own. ;)
Rust's behavior isn't unreasonable. It's just a potential missed optimization, but automating it is challenging.
- jeroenhd 2y agoAdding an extra scope here is slightly annoying, but it's not always possible. I think the example in the blog post was poorly chosen, because the complexity of BorrowedBuf together with MaybeUninit doesn't make much sense when your fix makes for much more readable code. Out of all problems I have encountered with Rust, this is a particularly minor one.
- auggierose 2y agoI see. It is a very bad example indeed. Terrible, terrible example. Switching off Trump mode for a moment, I don't see why you would want to declare the buffer inside the loop, given that keeping it alive for the entire time of the loop is actually the semantics you want.
- kmeisthax 2y agoIf people wrote the most optimal code the first time, we wouldn't need optimizing compilers, and all the undefined behavior that optimization passes necessarily bring along. The whole point of the example is to be poorly written in a way that the compiler "obviously" should be able to fix, but can't. A less obvious example would be... - A struct, A, which has an init_from_file method that deserializes data from a Read: R - Another struct, B, which has its own init_from_file, and a variable number of A as one of its fields. B::init_from_file needs to deserialize by calling A::init_from_file in a tight loop. This example is the same as the first, except now we've disguised the inefficiency with separation of concerns. A compiler can inline A::init_from_file into B::init_from_file to yield the same code as in the example.
- auggierose 2y agoSo you are saying that the buffer would be allocated in the program in A::init_from_file? And the compiler would be able to optimise that away by allocating the buffer outside the loop? If the compiler actually does that, that would be a good example. As long as I don't have to be careful to write my code in a way that some obscure compiler optimisation understands.
- dgacmu 2y agoBecause you want the buffer to go out of scope after the last iteration of the loop. Motivating that requires bringing in more rust - It could be as simple as wanting to reuse the variable name later, but more likely it would be because you were using something that had a reference that you wanted to go away so you could borrow it again without the borrow checker yelling at you.
- auggierose 2y agoOk, then the nested scope is indeed exactly what you want. I don't see how obfuscating this purpose and trying to rely on obscure compiler optimisations and intricate semantics would be a good idea.
- dgacmu 2y agoBecause, as someone else noted, it might be hidden for you because you're using something that's inside another function, macro, struct, etc.
- auggierose 2y agoYes, that is a valid reason. If I get that optimisation for free, why not? I still would not rely on that optimisation, though. If I think this could be an actual bottle neck, I would make the shared buffer explicit in my code.