fn main() {
let v;
for _ in 0..1 {
v = 0u8
}
println!("{v}")
}
Compiler said:
error[E0384]: cannot assign twice to immutable variable `v`
--> src/main.rs:4:9
| 4 | v = 0u8
| ^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut v;
| +++
error[E0381]: used binding `v` is possibly-uninitialized
--> src/main.rs:6:15
|
2 | let v;
| - binding declared here but left uninitialized
3 | for _ in 0..1 {
4 | v = 0u8
| ------- binding initialized here in some conditions
5 | }
6 | println!("{v}")
| ^^^ `v` used here but it is possibly-uninitialized
error[E0384]: cannot assign twice to immutable variable `v`
--> src/main.rs:4:9
|
4 | v = 0u8;
| ^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut v;
| +++
error[E0381]: used binding `v` is possibly-uninitialized
--> src/main.rs:6:15
|
2 | let v;
| - binding declared here but left uninitialized
3 | for _ in 0..1 {
| ---- if the `for` loop runs 0 times, `v` is not initialized
...
6 | println!("{v}")
| ^^^ `v` used here but it is possibly-uninitialized
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
Some errors have detailed explanations: E0381, E0384.
For more information about an error, try `rustc --explain E0381`.
fn main() {
let v;
if true {
v = 0u8
}
println!("{v}")
}
this doesn’t work, because the compiler has no magical special-casing for certain expressions like 0..1 on a loop or true on an if that would guarantee the block to be executed. It just follows the general rule that a loop might execute 0 times. And if you really need different behavior, you can always write an appropriate loop yourself.
For example, follow this kind of pattern for a loop that runs at least once:
fn main() {
let mut v;
// in general, also add `.into_iter()` call here if required
let mut iterator = 0..1;
// panics if iterator does not yield at least one item, after all
let mut next_item = iterator.next().expect("loop must run at least once");
loop {
// loop pattern `_` goes here
let _ = next_item;
// original loop body starts here
v = 0u8;
// original loop body ends here
match iterator.next() {
Some(n) => next_item = n,
None => break,
}
}
println!("{v}");
}
In some use-cases, usage of loop can also allow things like initializing only on the path that leads to a break anyways (in which case you need no unnecessary mut on the initialized variable).
Since Rust uses LLVM, if the source code initialize the variable by a useless value before writing useful value to that variable in for loop, can LLVM optimize out the initial useless value?
Depends on the specific iterator used, I’d say. With 0..1 for sure, LLVM will understand this (just like it also tends to unroll a loop over small fixed ranges like 0..5).
You can test e.g.
pub fn f() {
let mut v = 123_u8;
for _ in 0..1 {
v = 42_u8;
}
println!("{v}")
}
with the “Show Assembly” button in the playground, and see how in “Release” mode, any mention of the constant 123 is gone, as well as the jump instructions for the loop.
into Cargo.toml? These opt-level are to optimize binary size which isn't the default level (the default for release mode is opt-level=3), can LLVM still optimize out any unused values?
Also, it would be helpful if you described your actual use case for doing this. Obviously the stub demo here is better as just .last(), but I assume your real code wants to do something more interesting.
As far as I know, CPU uses complement codes for subtractions. Is addition always faster than subtraction since addition doesn't need complement codes? Maybe x86 CPUs use complement codes, how about other architectures, e.g., RISC-V, ARM, etc? Do RISC-V and ARM use complement codes so that addition is faster than subtraction?
Most CPUs take the same time for addition and subtraction - the extra complement in the subtraction path is simply dead time during an addition. For example, on this laptop (AMD), and on the CPUs I work with (RISC-V, ARM), addition and subtraction both take a single clock cycle; it's just that there's more timing slack in the addition path before you hit problems while overclocked.
In fact the simplest way to design an ALU is to have addition and subtraction done by the same circuit, with the same latency. The second operand bits are xor'ed with the "add/subtract" bit, and the same add/subtract bit is the initial carry.