Change:
This bug was . Filipe Rodrigues noticed that the initializer of a zero-length array is leaked: The code above does not run for , while I'd expect that it should. According to a comment by this regression occurred between Rust 1.11.0 and 1.12.0, i.e. when MIR code generation was enabled by default.
Minimized:
Marked as p-low as per the
This is a really weird corner case. If we modify the example slightly: them still moves ( cannot be used again). However, the storage for is destroyed once returns, so we would have to run the destructor when returns, not when is dropped. Effectively, the borrow-checker and the MIR drop logic disagree about who owns .
Regardless of what we end up doing, I think we should add a lint for this kind of scenario, as I think whatever behavior we use is going to be surprising.
I would not expect a destructor to run when returns and goes out of scope, because the value in has been moved. If we did not have array literal syntax and used a constructor instead, the value in would clearly be moved in the call and its ownership transferred to the constructor: When the array length is zero the repeating array "constructor" happens not to use the repeated value so it should be responsible for dropping it.
Agreed. Dropping should happen at the location of . I am not sure a lint is the right solution here? If we want to change MIR building (this looks like a clear bug to me), I do not see what a lint would achieve.
Hm, why does this even build? Usually array initializers only work for types. Is there a special case for when the array length is <= 1? I guess then we also need a special case during drop elaboration (or so) for when the array length is exactly 0? Cc (not sure who else to ping for MIR stuff)
Yup, it appears that is allowed to simply move a non-copy into an array: . Definitely a bit odd but I can certainly understand why this'd be the case.
I looked into fixing this by teaching parts of compiler that perform move based reasoning about special status of where length evaluates to zero. I had to change the move data gather to fix drop elaboration; transfer function used by const qualification so that it detects drop of a temporary in . Also, a few data flow analysis likely require changes as well, in particular and . Though, maybe that particular case of repeat should be lowered differently when building MIR so that it can be treated more uniformly later on?
thanks for looking into this! Once we are past drop elaboration, no special cases should be needed any more. But I am not sure how early/late in the pipeline that happens. Your list is somewhat concerning though, this sounds like it'd be very easy to miss a place and introduce a subtle bug. OTOH, we cannot really use different MIR for this -- with const generics, has to work. Though actually... that can only compile when . So maybe we can have a special case. Maybe we can make require , and use a different, more specialized primitive for the non- case? I wonder how that interacts with though.
To give a little more context to earlier comment, consider a MIR that before the drop elaboration looks as follows: Currently, the local is considered to be moved from and the drop elaboration removes the . The fix explored in , was to change move data so that the drop remains. This is not sufficient though, because remains and some analysis assign a special meaning to it. Consequently they require similar adjustments. For example, in const qualification ed locals are assumed not to need a drop. In they are assumed not to require a storage if they have not been borrowed.
I wonder if it would be simpler to modify the THIR -MIR lowering to treat as , and require that the MIR have a non-zero const value.
Yeah, I wondered the same -- so this sounds like a promising idea. That cannot work; for types might be a const generic so during THIR -MIR lowering we cannot say if it is 0. But we could require that non- have a non-zero const value (except const array repeat expressions might break this so they could be another exception).
I wondered about that too. So far I noticed that it would require additional changes to take into account lifetime extension / promotion, as direct implementation would allow:
Ah, interesting. We could probably allow this if we really wanted, or we could use a slightly different desugaring such as .
Shouldn't this be as similar to ? is an adaptation of for this bug.
Is a of any use anyway? I mean, the size is , so effectively a or /. might be a result of a typo in or something like that, so why not a hard error for that?
The type can be very useful (for example to unsize it to an empty slice of type ), but it’s typically created with a expression. This issue is about expressions which are indeed much less useful, but introducing new hard errors is a very big hammer and not necessary here. We only need to change such expressions from behaving like to behaving like .
I’m unfamiliar with the compiler-internal mechanisms that are relevant here. From a user-perspective, the situation is – in my opinion – as follows for , it feels unintuitive that the case would be special in instantiating the constant a different number than exactly times. I.e. I don’t think, should create an instance of (with the effect of calling the destructor once, too, if it has one). considering the case with constants described above, it does feel wrong that would move and drop . It if did, wouldn’t it also have to do this with constants to be consistent? I feel like it’s hard to find a convincing consistend approach here in general. afaict, the fact that and work with non--and-non- expressions it entirely useless in practice. You could always also write or instead! The only use-case of this that I can think of is an elsewhere-defined -item that’s specified to be either zero or one, but you don’t want to rely on which one of the two values it has, e.g. because the defining crate wouldn’t consider a change a breaking change. Then could make sense and can’t easily be replaced by or ; however would still be a valid fix/replacement in this rare situation. Or if that’s the intended behavior of the author. My personal conclusion would be to entirely remove the special capabilities of and and make sure they only compile successfully whenever would do so, too. In order to avoid breakage, one could introduce a warning in the current edition for any usage of or (or where is a constant that’s zero or one) whenever has a non- type and is not the path of a item make the warning a deny-by-default lint or a hard error in future editions also introduce a stronger (version of the) warning, that points our the concrete dangers of leaks, for the case of (or where is a constant that’s zero) whenever has a non- type and is not the path of a item or alternatively, at least whenever has a type that is not known to be free of drop-glue
If this is closed, the warning update on this page might be outdated :

Discussion:
}) } ExprKind::Repeat { value, count } => { <del> let value_operand = unpack!( block = this.as_operand(block, scope, &this.thir[value], None, NeedsTemporary::No) ); block.and(Rvalue::Repeat(value_operand, count)) </del> <ins> if Some(0) == count.try_eval_usize(this.tcx, this.param_env) { this.build_zero_repeat(block, value, scope, source_info) } else { let value_operand = unpack!( block = this.as_operand( block, scope, &this.thir[value], None, NeedsTemporary::No ) ); block.and(Rvalue::Repeat(value_operand, count)) } </ins> } ExprKind::Binary { op, lhs, rhs } => { let lhs = unpack!(

} } <ins> fn build_zero_repeat( &mut self, mut block: BasicBlock, value: ExprId, scope: Option<region::Scope>, outer_source_info: SourceInfo, ) -> BlockAnd<Rvalue<'tcx>> { let this = self; let value = &this.thir[value]; let elem_ty = value.ty; if let Some(Category::Constant) = Category::of(&value.kind) { // Repeating a const does nothing } else { // For a non-const, we may need to generate an appropriate `Drop` let value_operand = unpack!(block = this.as_operand(block, scope, value, None, NeedsTemporary::No)); if let Operand::Move(to_drop) = value_operand { let success = this.cfg.start_new_block(); this.cfg.terminate( block, outer_source_info, TerminatorKind::Drop { place: to_drop, target: success, unwind: None }, ); this.diverge_from(block); block = success; } this.record_operands_moved(&[value_operand]); } block.and(Rvalue::Aggregate(Box::new(AggregateKind::Array(elem_ty)), Vec::new())) } </ins> fn limit_capture_mutability( &mut self, upvar_span: Span,

self.cfg.block_data(start).terminator().kind, TerminatorKind::Assert { .. } | TerminatorKind::Call { .. } <ins> | TerminatorKind::Drop { .. } </ins> | TerminatorKind::DropAndReplace { .. } | TerminatorKind::FalseUnwind { .. } | TerminatorKind::InlineAsm { .. }

<ins> fn borrowck_catch() { let foo = String::new(); let _bar = foo; let _baz = [foo; 0]; //~ ERROR use of moved value: `foo` [E0382] } const _: [String; 0] = [String::new(); 0]; //~^ ERROR destructors cannot be evaluated at compile-time [E0493] fn must_be_init() { let x: u8; let _ = [x; 0]; //~ ERROR: use of possibly-uninitialized variable: `x` } fn main() {} </ins>

<ins> error[E0382]: use of moved value: `foo` --> $DIR/repeat-drop-2.rs:4:17 | LL |     let foo = String::new(); |         --- move occurs because `foo` has type `String`, which does not implement the `Copy` trait LL |     let _bar = foo; |                --- value moved here LL |     let _baz = [foo; 0]; |                 ^^^ value used here after move error[E0493]: destructors cannot be evaluated at compile-time --> $DIR/repeat-drop-2.rs:7:25 | LL | const _: [String; 0] = [String::new(); 0]; |                        -^^^^^^^^^^^^^---- |                        || |                        |constants cannot evaluate destructors |                        value is dropped here error[E0381]: use of possibly-uninitialized variable: `x` --> $DIR/repeat-drop-2.rs:12:14 | LL |     let _ = [x; 0]; |              ^ use of possibly-uninitialized `x` error: aborting due to 3 previous errors Some errors have detailed explanations: E0381, E0382, E0493. For more information about an error, try `rustc --explain E0381`. </ins>

<ins> // run-pass // ignore-wasm32-bare no unwinding panic // ignore-avr no unwinding panic // ignore-nvptx64 no unwinding panic static mut CHECK: usize = 0; struct DropChecker(usize); impl Drop for DropChecker { fn drop(&mut self) { unsafe { if CHECK != self.0 - 1 { panic!("Found {}, should have found {}", CHECK, self.0 - 1); } CHECK = self.0; } } } macro_rules! check_drops { ($l:literal) => { unsafe { assert_eq!(CHECK, $l) } }; } struct DropPanic; impl Drop for DropPanic { fn drop(&mut self) { panic!() } } fn value_zero() { unsafe { CHECK = 0 }; let foo = DropChecker(1); let v: [DropChecker; 0] = [foo; 0]; check_drops!(1); std::mem::drop(v); check_drops!(1); } fn value_one() { unsafe { CHECK = 0 }; let foo = DropChecker(1); let v: [DropChecker; 1] = [foo; 1]; check_drops!(0); std::mem::drop(v); check_drops!(1); } const DROP_CHECKER: DropChecker = DropChecker(1); fn const_zero() { unsafe { CHECK = 0 }; let v: [DropChecker; 0] = [DROP_CHECKER; 0]; check_drops!(0); std::mem::drop(v); check_drops!(0); } fn const_one() { unsafe { CHECK = 0 }; let v: [DropChecker; 1] = [DROP_CHECKER; 1]; check_drops!(0); std::mem::drop(v); check_drops!(1); } fn const_generic_zero<const N: usize>() { unsafe { CHECK = 0 }; let v: [DropChecker; N] = [DROP_CHECKER; N]; check_drops!(0); std::mem::drop(v); check_drops!(0); } fn const_generic_one<const N: usize>() { unsafe { CHECK = 0 }; let v: [DropChecker; N] = [DROP_CHECKER; N]; check_drops!(0); std::mem::drop(v); check_drops!(1); } // Make sure that things are allowed to promote as expected fn allow_promote() { unsafe { CHECK = 0 }; let foo = DropChecker(1); let v: &'static [DropChecker; 0] = &[foo; 0]; check_drops!(1); std::mem::drop(v); check_drops!(1); } // Verify that unwinding in the drop causes the right things to drop in the right order fn on_unwind() { unsafe { CHECK = 0 }; std::panic::catch_unwind(|| { let panic = DropPanic; let _local = DropChecker(2); let _v = (DropChecker(1), [panic; 0]); std::process::abort(); }) .unwrap_err(); check_drops!(2); } fn main() { value_zero(); value_one(); const_zero(); const_one(); const_generic_zero::<0>(); const_generic_one::<1>(); allow_promote(); on_unwind(); } </ins>