diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 0be00f4d00be7..a6e63214b3bf3 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1594,6 +1594,7 @@ impl Expr { // need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b` // but we need to print `(let _ = a) < b` as-is with parens. | ExprKind::Let(..) + | ExprKind::Move(..) | ExprKind::Unary(..) => ExprPrecedence::Prefix, // Need parens if and only if there are prefix attributes. @@ -1763,6 +1764,8 @@ pub enum ExprKind { Binary(BinOp, Box, Box), /// A unary operation (e.g., `!x`, `*x`). Unary(UnOp, Box), + /// A `move(expr)` expression. + Move(Box, Span), /// A literal (e.g., `1`, `"foo"`). Lit(token::Lit), /// A cast (e.g., `foo as f64`). diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 43ef6897b79cf..0c98b0e5e7b4f 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -108,6 +108,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { Assign(e, _, _) | AssignOp(_, e, _) | Await(e, _) + | Move(e, _) | Use(e, _) | Binary(_, e, _) | Call(e, _) @@ -183,6 +184,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { | Ret(Some(e)) | Unary(_, e) | Yeet(Some(e)) + | Move(e, _) | Become(e) => { expr = e; } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index cbd7cb3ee8c6f..ed8c3787bfec4 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -1024,7 +1024,9 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, block, opt_label), ExprKind::Gen(capt, body, kind, decl_span) => visit_visitable!($($mut)? vis, capt, body, kind, decl_span), - ExprKind::Await(expr, span) | ExprKind::Use(expr, span) => + ExprKind::Await(expr, span) + | ExprKind::Move(expr, span) + | ExprKind::Use(expr, span) => visit_visitable!($($mut)? vis, expr, span), ExprKind::Assign(lhs, rhs, span) => visit_visitable!($($mut)? vis, lhs, rhs, span), diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 95b8bb48c6a9c..a1c1d1e11d694 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -136,6 +136,13 @@ pub(crate) struct ClosureCannotBeStatic { pub fn_decl_span: Span, } +#[derive(Diagnostic)] +#[diag("`move(expr)` is only supported in plain closures")] +pub(crate) struct MoveExprOnlyInPlainClosures { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("functional record updates are not allowed in destructuring assignments")] pub(crate) struct FunctionalRecordUpdateDestructuringAssignment { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index eaa22e071af4b..9fc69470cf546 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -2,6 +2,7 @@ use std::mem; use std::ops::ControlFlow; use std::sync::Arc; +use rustc_ast::node_id::NodeMap; use rustc_ast::*; use rustc_ast_pretty::pprust::expr_to_string; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -19,8 +20,8 @@ use visit::{Visitor, walk_expr}; use super::errors::{ AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, ClosureCannotBeStatic, CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment, - InclusiveRangeWithNoEnd, MatchArmWithNoBody, NeverPatternWithBody, NeverPatternWithGuard, - UnderscoreExprLhsAssign, + InclusiveRangeWithNoEnd, MatchArmWithNoBody, MoveExprOnlyInPlainClosures, NeverPatternWithBody, + NeverPatternWithGuard, UnderscoreExprLhsAssign, }; use super::{ GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode, ResolverAstLoweringExt, @@ -30,6 +31,68 @@ use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScop pub(super) struct WillCreateDefIdsVisitor; +/// A `move(...)` expression found while looking up generated initializers. +struct MoveExprInitializer<'a> { + /// The `NodeId` of the outer `move(...)` expression. + id: NodeId, + /// Span of the `move` token, used for the generated binding name. + move_kw_span: Span, + /// The expression inside `move(...)`; e.g. `foo.bar` in `move(foo.bar)`. + expr: &'a Expr, +} + +/// State for `move(...)` expressions found while lowering one plain closure body. +pub(super) struct MoveExprState<'hir> { + pub(super) bindings: NodeMap<(Ident, HirId)>, + pub(super) occurrences: Vec>, +} + +impl<'hir> Default for MoveExprState<'hir> { + fn default() -> Self { + Self { bindings: NodeMap::default(), occurrences: Vec::new() } + } +} + +pub(super) struct MoveExprOccurrence<'hir> { + id: NodeId, + ident: Ident, + pat: &'hir hir::Pat<'hir>, + binding: HirId, + explicit_capture: bool, +} + +/// Looks up the initializer expression for each `move(...)` occurrence. +struct MoveExprInitializerFinder<'a> { + initializers: Vec>, +} + +impl<'a> MoveExprInitializerFinder<'a> { + fn collect(expr: &'a Expr) -> Vec> { + let mut this = Self { initializers: Vec::new() }; + this.visit_expr(expr); + this.initializers + } +} + +impl<'a> Visitor<'a> for MoveExprInitializerFinder<'a> { + fn visit_expr(&mut self, expr: &'a Expr) { + match &expr.kind { + ExprKind::Move(inner, move_kw_span) => { + self.visit_expr(inner); + self.initializers.push(MoveExprInitializer { + id: expr.id, + move_kw_span: *move_kw_span, + expr: inner, + }); + } + ExprKind::Closure(..) | ExprKind::Gen(..) | ExprKind::ConstBlock(..) => {} + _ => walk_expr(self, expr), + } + } + + fn visit_item(&mut self, _: &'a Item) {} +} + impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor { type Result = ControlFlow; @@ -52,6 +115,42 @@ impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor { } impl<'hir> LoweringContext<'_, 'hir> { + fn with_move_expr_bindings( + &mut self, + state: Option>, + f: impl FnOnce(&mut Self) -> T, + ) -> (T, Option>) { + self.move_expr_bindings.push(state); + let result = f(self); + let state = self.move_expr_bindings.pop().unwrap_or_else(|| { + span_bug!(DUMMY_SP, "`move_expr_bindings` stack was empty after lowering") + }); + (result, state) + } + + fn record_move_expr( + &mut self, + id: NodeId, + inner: &Expr, + move_kw_span: Span, + explicit_capture: bool, + ) -> (Ident, HirId) { + let index = self + .move_expr_bindings + .last() + .and_then(|state| state.as_ref()) + .map_or(0, |state| state.occurrences.len()); + let ident = Ident::from_str_and_span(&format!("__move_expr_{index}"), move_kw_span); + let (pat, binding) = self.pat_ident(inner.span, ident); + let Some(state) = self.move_expr_bindings.last_mut().and_then(|state| state.as_mut()) + else { + span_bug!(move_kw_span, "`move(...)` lowered without a plain closure body state"); + }; + state.bindings.insert(id, (ident, binding)); + state.occurrences.push(MoveExprOccurrence { id, ident, pat, binding, explicit_capture }); + (ident, binding) + } + fn lower_exprs(&mut self, exprs: &[Box]) -> &'hir [hir::Expr<'hir>] { self.arena.alloc_from_iter(exprs.iter().map(|x| self.lower_expr_mut(x))) } @@ -94,11 +193,12 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::ForLoop { pat, iter, body, label, kind } => { return self.lower_expr_for(e, pat, iter, body, *label, *kind); } + ExprKind::Closure(closure) => return self.lower_expr_closure_expr(e, closure), _ => (), } let expr_hir_id = self.lower_node_id(e.id); - let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); + self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); let kind = match &e.kind { ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)), @@ -211,43 +311,46 @@ impl<'hir> LoweringContext<'_, 'hir> { }, ), ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr), + ExprKind::Move(inner, move_kw_span) => { + if !self.tcx.features().move_expr() { + return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap()); + } + if let Some(state) = self.move_expr_bindings.last().and_then(Option::as_ref) { + let existing = state.bindings.get(&e.id).copied(); + let (ident, binding) = existing.unwrap_or_else(|| { + for nested in MoveExprInitializerFinder::collect(inner) { + self.record_move_expr( + nested.id, + nested.expr, + nested.move_kw_span, + false, + ); + } + self.record_move_expr(e.id, inner, *move_kw_span, true) + }); + hir::ExprKind::Path(hir::QPath::Resolved( + None, + self.arena.alloc(hir::Path { + span: self.lower_span(e.span), + res: Res::Local(binding), + segments: arena_vec![ + self; + hir::PathSegment::new( + self.lower_ident(ident), + self.next_id(), + Res::Local(binding), + ) + ], + }), + )) + } else { + let guar = self + .dcx() + .emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span }); + hir::ExprKind::Err(guar) + } + } ExprKind::Use(expr, use_kw_span) => self.lower_expr_use(*use_kw_span, expr), - ExprKind::Closure(Closure { - binder, - capture_clause, - constness, - coroutine_kind, - movability, - fn_decl, - body, - fn_decl_span, - fn_arg_span, - }) => match coroutine_kind { - Some(coroutine_kind) => self.lower_expr_coroutine_closure( - binder, - *capture_clause, - e.id, - expr_hir_id, - *coroutine_kind, - *constness, - fn_decl, - body, - *fn_decl_span, - *fn_arg_span, - ), - None => self.lower_expr_closure( - attrs, - binder, - *capture_clause, - e.id, - *constness, - *movability, - fn_decl, - body, - *fn_decl_span, - *fn_arg_span, - ), - }, ExprKind::Gen(capture_clause, block, genblock_kind, decl_span) => { let desugaring_kind = match genblock_kind { GenBlockKind::Async => hir::CoroutineDesugaring::Async, @@ -262,7 +365,14 @@ impl<'hir> LoweringContext<'_, 'hir> { e.span, desugaring_kind, hir::CoroutineSource::Block, - |this| this.with_new_scopes(e.span, |this| this.lower_block_expr(block)), + |this| { + this.with_new_scopes(e.span, |this| { + let (expr, _) = this.with_move_expr_bindings(None, |this| { + this.lower_block_expr(block) + }); + expr + }) + }, ) } ExprKind::Block(blk, opt_label) => { @@ -382,7 +492,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr), - ExprKind::Paren(_) | ExprKind::ForLoop { .. } => { + ExprKind::Paren(_) | ExprKind::ForLoop { .. } | ExprKind::Closure(..) => { unreachable!("already handled") } @@ -396,11 +506,11 @@ impl<'hir> LoweringContext<'_, 'hir> { pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock { self.with_new_scopes(c.value.span, |this| { let def_id = this.local_def_id(c.id); - hir::ConstBlock { - def_id, - hir_id: this.lower_node_id(c.id), - body: this.lower_const_body(c.value.span, Some(&c.value)), - } + let hir_id = this.lower_node_id(c.id); + let (body, _) = this.with_move_expr_bindings(None, |this| { + this.lower_const_body(c.value.span, Some(&c.value)) + }); + hir::ConstBlock { def_id, hir_id, body } }) } @@ -783,6 +893,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span: None, kind: hir::ClosureKind::Coroutine(coroutine_kind), constness: hir::Constness::NotConst, + explicit_captures: &[], })) } @@ -1046,6 +1157,132 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span)) } + // Lowers closure expressions, including the `move(...)` desugaring for + // plain closures. + fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> { + let expr_hir_id = self.lower_node_id(e.id); + let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); + + match closure.coroutine_kind { + // FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too. + // For the first step, we only support plain closures. + Some(coroutine_kind) => hir::Expr { + hir_id: expr_hir_id, + kind: self.lower_expr_coroutine_closure( + &closure.binder, + closure.capture_clause, + e.id, + expr_hir_id, + coroutine_kind, + closure.constness, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + ), + span: self.lower_span(e.span), + }, + None => self.lower_expr_plain_closure_with_move_exprs( + expr_hir_id, + attrs, + &closure.binder, + closure.capture_clause, + e.id, + closure.constness, + closure.movability, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + e.span, + ), + } + } + + fn lower_expr_plain_closure_with_move_exprs( + &mut self, + expr_hir_id: HirId, + attrs: &[rustc_hir::Attribute], + binder: &ClosureBinder, + capture_clause: CaptureBy, + closure_id: NodeId, + constness: Const, + movability: Movability, + decl: &FnDecl, + body: &Expr, + fn_decl_span: Span, + fn_arg_span: Span, + whole_span: Span, + ) -> hir::Expr<'hir> { + let (closure_kind, move_expr_state) = self.lower_expr_closure( + attrs, + binder, + capture_clause, + closure_id, + constness, + movability, + decl, + body, + fn_decl_span, + fn_arg_span, + ); + + if move_expr_state.occurrences.is_empty() { + return hir::Expr { + hir_id: expr_hir_id, + kind: closure_kind, + span: self.lower_span(whole_span), + }; + } + + let initializers = MoveExprInitializerFinder::collect(body) + .into_iter() + .map(|initializer| (initializer.id, initializer.expr)) + .collect::>(); + let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len()); + let mut initializer_bindings = NodeMap::default(); + for occurrence in &move_expr_state.occurrences { + // Evaluate the expression inside `move(...)` before creating the + // closure and store it in a synthetic local: + // `|| move(foo).bar` becomes roughly + // `let __move_expr_0 = foo; || __move_expr_0.bar`. + let expr = initializers[&occurrence.id]; + let init = if initializer_bindings.is_empty() { + self.lower_expr(expr) + } else { + // Earlier entries cover nested `move(...)` expressions that + // appear inside this initializer, as in + // `move(move(foo.clone()))`. + let (init, _) = self.with_move_expr_bindings( + Some(MoveExprState { + bindings: initializer_bindings.clone(), + occurrences: Vec::new(), + }), + |this| this.lower_expr(expr), + ); + init + }; + stmts.push(self.stmt_let_pat( + None, + expr.span, + Some(init), + occurrence.pat, + hir::LocalSource::Normal, + )); + initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding)); + } + + let closure_expr = self.arena.alloc(hir::Expr { + hir_id: expr_hir_id, + kind: closure_kind, + span: self.lower_span(whole_span), + }); + + let stmts = self.arena.alloc_from_iter(stmts); + let block = self.block_all(whole_span, stmts, Some(closure_expr)); + self.expr(whole_span, hir::ExprKind::Block(block, None)) + } + fn lower_expr_closure( &mut self, attrs: &[rustc_hir::Attribute], @@ -1058,25 +1295,37 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Expr, fn_decl_span: Span, fn_arg_span: Span, - ) -> hir::ExprKind<'hir> { + ) -> (hir::ExprKind<'hir>, MoveExprState<'hir>) { let closure_def_id = self.local_def_id(closure_id); let (binder_clause, generic_params) = self.lower_closure_binder(binder); - let (body_id, closure_kind) = self.with_new_scopes(fn_decl_span, move |this| { + let ((body_id, closure_kind), move_expr_state) = self.with_new_scopes(fn_decl_span, move |this| { let mut coroutine_kind = find_attr!(attrs, Coroutine => hir::CoroutineKind::Coroutine(Movability::Movable)); - // FIXME(contracts): Support contracts on closures? - let body_id = this.lower_fn_body(decl, None, |this| { - this.coroutine_kind = coroutine_kind; - let e = this.lower_expr_mut(body); - coroutine_kind = this.coroutine_kind; - e - }); - let coroutine_option = - this.closure_movability_for_fn(decl, fn_decl_span, coroutine_kind, movability); - (body_id, coroutine_option) + this.with_move_expr_bindings(Some(MoveExprState::default()), |this| { + // FIXME(contracts): Support contracts on closures? + let body_id = this.lower_fn_body(decl, None, |this| { + this.coroutine_kind = coroutine_kind; + let e = this.lower_expr_mut(body); + coroutine_kind = this.coroutine_kind; + e + }); + let coroutine_option = + this.closure_movability_for_fn(decl, fn_decl_span, coroutine_kind, movability); + (body_id, coroutine_option) + }) }); + let Some(move_expr_state) = move_expr_state else { + span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state"); + }; + let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter( + move_expr_state.occurrences.iter().filter_map(|occurrence| { + occurrence + .explicit_capture + .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding }) + }), + ); let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); // Lower outside new scope to preserve `is_in_loop_condition`. @@ -1093,9 +1342,10 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span: Some(self.lower_span(fn_arg_span)), kind: closure_kind, constness: self.lower_constness(constness), + explicit_captures, }); - hir::ExprKind::Closure(c) + (hir::ExprKind::Closure(c), move_expr_state) } fn closure_movability_for_fn( @@ -1174,14 +1424,16 @@ impl<'hir> LoweringContext<'_, 'hir> { // Transform `async |x: u8| -> X { ... }` into // `|x: u8| || -> X { ... }`. let body_id = this.lower_body(|this| { - let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments( - &inner_decl, - |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), - fn_decl_span, - body.span, - coroutine_kind, - hir::CoroutineSource::Closure, - ); + let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| { + this.lower_coroutine_body_with_moved_arguments( + &inner_decl, + |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), + fn_decl_span, + body.span, + coroutine_kind, + hir::CoroutineSource::Closure, + ) + }); this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id); @@ -1215,6 +1467,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // "coroutine that returns &str", rather than directly returning a `&str`. kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring), constness: self.lower_constness(constness), + explicit_captures: &[], }); hir::ExprKind::Closure(c) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 1ce4478c09e8b..d8a7b6e93ab0c 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -158,6 +158,10 @@ struct LoweringContext<'a, 'hir> { allow_async_fn_traits: Arc<[Symbol]>, delayed_lints: Vec, + /// Stack of `move(...)` collection states. A plain closure body pushes + /// `Some`, so `move(...)` expressions can record the generated locals they + /// should lower to. Nested bodies that cannot use `move(...)` push `None`. + move_expr_bindings: Vec>>, attribute_parser: AttributeParser<'hir>, } @@ -216,6 +220,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // interact with `gen`/`async gen` blocks allow_async_iterator: [sym::gen_future, sym::async_iterator].into(), + move_expr_bindings: Vec::new(), attribute_parser: AttributeParser::new( tcx.sess, tcx.features(), diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index e38716cbb7fb7..b43dbad611a0e 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -503,6 +503,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(impl_restriction, "`impl` restrictions are experimental"); gate_all!(min_generic_const_args, "unbraced const blocks as const args are experimental"); gate_all!(more_qualified_paths, "usage of qualified paths in this context is experimental"); + gate_all!(move_expr, "`move(expr)` syntax is experimental"); gate_all!(mut_ref, "mutable by-reference bindings are experimental"); gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(postfix_match, "postfix match is experimental"); diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index a20ef210da973..78aedbd4f7f4b 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -630,6 +630,11 @@ impl<'a> State<'a> { ); self.word(".await"); } + ast::ExprKind::Move(expr, _) => { + self.word("move("); + self.print_expr(expr, FixupContext::default()); + self.word(")"); + } ast::ExprKind::Use(expr, _) => { self.print_expr_cond_paren( expr, diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 6ad9c61840fae..f15acc154baf3 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -248,6 +248,9 @@ impl<'cx, 'a> Context<'cx, 'a> { self.manage_cond_expr(arg); } } + ExprKind::Move(local_expr, _) => { + self.manage_cond_expr(local_expr); + } ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => { let path_ident = path_segment.ident; self.manage_initial_capture(expr, path_ident); diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 5af522486b1f6..17f84154b9fff 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -638,6 +638,8 @@ declare_features! ( (unstable, mips_target_feature, "1.27.0", Some(150253)), /// Allows qualified paths in struct expressions, struct patterns and tuple struct patterns. (unstable, more_qualified_paths, "1.54.0", Some(86935)), + /// Allows `move(expr)` in closures. + (incomplete, move_expr, "CURRENT_RUSTC_VERSION", Some(155050)), /// The `movrs` target feature on x86. (unstable, movrs_target_feature, "1.88.0", Some(137976)), /// Allows the `multiple_supertrait_upcastable` lint. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 2f18b09cf1ae8..84836e2a25c49 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1682,6 +1682,14 @@ pub struct Closure<'hir> { /// The span of the argument block `|...|` pub fn_arg_span: Option, pub kind: ClosureKind, + pub explicit_captures: &'hir [ExplicitCapture], +} + +/// A HIR local that must be captured by value even if ordinary closure capture +/// analysis would infer a weaker capture kind from its uses in the body. +#[derive(Debug, Clone, Copy, StableHash)] +pub struct ExplicitCapture { + pub var_hir_id: HirId, } #[derive(Clone, PartialEq, Eq, Debug, Copy, Hash, StableHash, Encodable, Decodable)] diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 68d8c12ec099c..85f0e97a0a9d6 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -893,6 +893,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) fn_arg_span: _, kind: _, constness: _, + explicit_captures: _, }) => { walk_list!(visitor, visit_generic_param, bound_generic_params); try_visit!(visitor.visit_fn(FnKind::Closure, fn_decl, body, *span, def_id)); diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 1a401af1d328d..637ae115131a1 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1645,6 +1645,7 @@ impl<'a> State<'a> { fn_arg_span: _, kind: _, def_id: _, + explicit_captures: _, }) => { self.print_closure_binder(binder, bound_generic_params); self.print_constness(constness); diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index dfdc7c3d8df0a..25b5540457f19 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -54,6 +54,7 @@ use tracing::{debug, instrument}; use super::FnCtxt; use crate::expr_use_visitor as euv; +use crate::expr_use_visitor::Delegate as _; /// Describe the relationship between the paths of two places /// eg: @@ -206,8 +207,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fake_reads: Default::default(), }; + // First collect the captures implied by the operations in the closure + // body. This records how each place is actually used: borrowed, modified, + // moved, and so on. let _ = euv::ExprUseVisitor::new(&closure_fcx, &mut delegate).consume_body(body); + // `consume_body` only sees how the lowered closure body uses those + // places. For `move(foo).clone()`, the body may only borrow the + // synthetic local for `foo`, but the source `move(...)` still requires + // capturing that local by value. + let explicit_captures = match self.tcx.hir_node(closure_hir_id).expect_expr().kind { + hir::ExprKind::Closure(closure) => closure.explicit_captures, + _ => bug!("expected closure expr for {:?}", closure_hir_id), + }; + for capture in explicit_captures { + let place = closure_fcx.place_for_root_variable(closure_def_id, capture.var_hir_id); + delegate.consume(&PlaceWithHirId { hir_id: capture.var_hir_id, place }, closure_hir_id); + } + // There are several curious situations with coroutine-closures where // analysis is too aggressive with borrows when the coroutine-closure is // marked `move`. Specifically: diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 35c271cb70204..e5de0d972df6a 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -564,6 +564,12 @@ impl<'a> Parser<'a> { token::Ident(..) if this.token.is_keyword(kw::Box) => { make_it!(this, attrs, |this, _| this.parse_expr_box(lo)) } + token::Ident(..) + if this.token.is_keyword(kw::Move) + && this.look_ahead(1, |t| *t == token::OpenParen) => + { + make_it!(this, attrs, |this, _| this.parse_expr_move(lo)) + } token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => { make_it!(this, attrs, |this, _| this.recover_not_expr(lo)) } @@ -607,6 +613,16 @@ impl<'a> Parser<'a> { Ok((span, ExprKind::Err(guar))) } + fn parse_expr_move(&mut self, move_kw: Span) -> PResult<'a, (Span, ExprKind)> { + self.bump(); + self.psess.gated_spans.gate(sym::move_expr, move_kw); + self.expect(exp!(OpenParen))?; + let expr = self.parse_expr()?; + self.expect(exp!(CloseParen))?; + let span = move_kw.to(self.prev_token.span); + Ok((span, ExprKind::Move(expr, move_kw))) + } + fn is_mistaken_not_ident_negation(&self) -> bool { let token_cannot_continue_expr = |t: &Token| match t.uninterpolate().kind { // These tokens can start an expression after `!`, but @@ -4387,6 +4403,7 @@ impl MutVisitor for CondChecker<'_> { } ExprKind::Unary(_, _) | ExprKind::Await(_, _) + | ExprKind::Move(_, _) | ExprKind::Use(_, _) | ExprKind::AssignOp(_, _, _) | ExprKind::Range(_, _, _) diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index e424cc09fb607..9127e4936803d 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -657,7 +657,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { (self, e, e.kind, None, ast, Expr, ExprKind), [ Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, - If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign, + If, While, ForLoop, Loop, Match, Closure, Block, Await, Move, Use, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ea2880d8d7dff..e7cc43fdf8ebc 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1323,6 +1323,7 @@ symbols! { more_qualified_paths, more_struct_aliases, movbe_target_feature, + move_expr, move_ref_pattern, move_size_limit, movrs_target_feature, diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 92f08b604ca58..f194103ae2e88 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -231,6 +231,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Loop(..) | ast::ExprKind::MacCall(..) | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Move(..) | ast::ExprKind::Paren(..) | ast::ExprKind::Underscore | ast::ExprKind::Path(..) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index d34706a2ba5cd..8a3674bff1ca6 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -125,6 +125,16 @@ pub(crate) fn format_expr( let callee_str = callee.rewrite_result(context, shape)?; rewrite_call(context, &callee_str, args, inner_span, shape) } + ast::ExprKind::Move(ref subexpr, move_kw_span) => { + let inner_span = mk_sp(move_kw_span.hi(), expr.span.hi()); + rewrite_call( + context, + "move", + std::slice::from_ref(subexpr), + inner_span, + shape, + ) + } ast::ExprKind::Paren(ref subexpr) => rewrite_paren(context, subexpr, shape, expr.span), ast::ExprKind::Binary(op, ref lhs, ref rhs) => { // FIXME: format comments between operands and operator diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index b052e74d8bf20..de72c9ce14bc3 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -553,6 +553,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr | ast::ExprKind::Field(..) | ast::ExprKind::IncludedBytes(..) | ast::ExprKind::InlineAsm(..) + | ast::ExprKind::Move(..) | ast::ExprKind::OffsetOf(..) | ast::ExprKind::UnsafeBinderCast(..) | ast::ExprKind::Let(..) diff --git a/tests/ui/README.md b/tests/ui/README.md index 2fe1657e7ecf2..1ab22a65ce507 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -927,6 +927,10 @@ Tests on the module system. **FIXME**: `tests/ui/imports/` should probably be merged with this. +## `tests/ui/move-expr/` + +Tests for `#![feature(move_expr)]`. + ## `tests/ui/moves` Tests on moves (destructive moves). diff --git a/tests/ui/feature-gates/feature-gate-move_expr.rs b/tests/ui/feature-gates/feature-gate-move_expr.rs new file mode 100644 index 0000000000000..a2ab1bb8b1d00 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-move_expr.rs @@ -0,0 +1,4 @@ +fn main() { + let _ = || move(2); + //~^ ERROR `move(expr)` syntax is experimental +} diff --git a/tests/ui/feature-gates/feature-gate-move_expr.stderr b/tests/ui/feature-gates/feature-gate-move_expr.stderr new file mode 100644 index 0000000000000..8b1da2c06893d --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-move_expr.stderr @@ -0,0 +1,13 @@ +error[E0658]: `move(expr)` syntax is experimental + --> $DIR/feature-gate-move_expr.rs:2:16 + | +LL | let _ = || move(2); + | ^^^^ + | + = note: see issue #155050 for more information + = help: add `#![feature(move_expr)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/macros/stringify.rs b/tests/ui/macros/stringify.rs index b48b037b22949..fec991ec95b7b 100644 --- a/tests/ui/macros/stringify.rs +++ b/tests/ui/macros/stringify.rs @@ -3,6 +3,7 @@ //@ compile-flags: --test #![allow(incomplete_features)] +#![allow(unused_features)] #![feature(auto_traits)] #![feature(box_patterns)] #![feature(const_block_items)] @@ -11,6 +12,7 @@ #![feature(decl_macro)] #![feature(macro_guard_matcher)] #![feature(more_qualified_paths)] +#![feature(move_expr)] #![feature(never_patterns)] #![feature(specialization)] #![feature(trait_alias)] @@ -110,6 +112,7 @@ fn test_expr() { c1!(expr, [ *expr ], "*expr"); c1!(expr, [ !expr ], "!expr"); c1!(expr, [ -expr ], "-expr"); + c1!(expr, [ move(expr) ], "move(expr)"); // ExprKind::Lit c1!(expr, [ 'x' ], "'x'"); diff --git a/tests/ui/move-expr/borrow-only.rs b/tests/ui/move-expr/borrow-only.rs new file mode 100644 index 0000000000000..f5c6ba228cfb7 --- /dev/null +++ b/tests/ui/move-expr/borrow-only.rs @@ -0,0 +1,13 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = vec![1, 2, 3]; + let c = || { + let t = &move(s); + println!("{t:?}"); + }; + + c(); +} diff --git a/tests/ui/move-expr/capture-reference.rs b/tests/ui/move-expr/capture-reference.rs new file mode 100644 index 0000000000000..a4c2caee57052 --- /dev/null +++ b/tests/ui/move-expr/capture-reference.rs @@ -0,0 +1,15 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let c = { + let x = 22; + || { + let y = move(&x); + //~^ ERROR `x` does not live long enough + println!("{y:?}"); + } + }; + + c(); +} diff --git a/tests/ui/move-expr/capture-reference.stderr b/tests/ui/move-expr/capture-reference.stderr new file mode 100644 index 0000000000000..8b970e98d6bdb --- /dev/null +++ b/tests/ui/move-expr/capture-reference.stderr @@ -0,0 +1,17 @@ +error[E0597]: `x` does not live long enough + --> $DIR/capture-reference.rs:8:26 + | +LL | let c = { + | - borrow later captured here by closure +LL | let x = 22; + | - binding `x` declared here +LL | || { +LL | let y = move(&x); + | ^^ borrowed value does not live long enough +... +LL | }; + | - `x` dropped here while still borrowed + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/move-expr/copy-type.rs b/tests/ui/move-expr/copy-type.rs new file mode 100644 index 0000000000000..4cc790f7103ff --- /dev/null +++ b/tests/ui/move-expr/copy-type.rs @@ -0,0 +1,15 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = 22; + let c = || { + let y = move(x); + let z = x; + assert_eq!(y + z, 44); + }; + + c(); + c(); +} diff --git a/tests/ui/move-expr/double-move.rs b/tests/ui/move-expr/double-move.rs new file mode 100644 index 0000000000000..2d4beb2246e1e --- /dev/null +++ b/tests/ui/move-expr/double-move.rs @@ -0,0 +1,13 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = vec![1, 2, 3]; + let _c = || { + let y = move(x); + let z = move(x); + //~^ ERROR use of moved value: `x` + drop(y); + drop(z); + }; +} diff --git a/tests/ui/move-expr/double-move.stderr b/tests/ui/move-expr/double-move.stderr new file mode 100644 index 0000000000000..ddb01814f7260 --- /dev/null +++ b/tests/ui/move-expr/double-move.stderr @@ -0,0 +1,23 @@ +error[E0382]: use of moved value: `x` + --> $DIR/double-move.rs:8:22 + | +LL | let x = vec![1, 2, 3]; + | - move occurs because `x` has type `Vec`, which does not implement the `Copy` trait +LL | let _c = || { +LL | let y = move(x); + | - value moved here +LL | let z = move(x); + | ^ value used here after move + | +help: consider cloning the value if the performance cost is acceptable + | +LL | let y = move(x.clone()); + | ++++++++ +help: borrow this binding in the pattern to avoid moving the value + | +LL | let y = move(ref x); + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/move-expr/move-fnonce.rs b/tests/ui/move-expr/move-fnonce.rs new file mode 100644 index 0000000000000..a9d44cad19502 --- /dev/null +++ b/tests/ui/move-expr/move-fnonce.rs @@ -0,0 +1,14 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = vec![1, 2, 3]; + let mut c = || { + let t = move(s); + println!("{t:?}"); + }; + + c(); + c(); + //~^ ERROR use of moved value: `c` +} diff --git a/tests/ui/move-expr/move-fnonce.stderr b/tests/ui/move-expr/move-fnonce.stderr new file mode 100644 index 0000000000000..635b3cd6a4fa2 --- /dev/null +++ b/tests/ui/move-expr/move-fnonce.stderr @@ -0,0 +1,22 @@ +error[E0382]: use of moved value: `c` + --> $DIR/move-fnonce.rs:12:5 + | +LL | c(); + | --- `c` moved due to this call +LL | c(); + | ^ value used here after move + | +note: closure cannot be invoked more than once because it moves the variable `__move_expr_0` out of its environment + --> $DIR/move-fnonce.rs:7:17 + | +LL | let t = move(s); + | ^^^^^^^ +note: this value implements `FnOnce`, which causes it to be moved when called + --> $DIR/move-fnonce.rs:11:5 + | +LL | c(); + | ^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/move-expr/name-resolution.rs b/tests/ui/move-expr/name-resolution.rs new file mode 100644 index 0000000000000..7c0886183eb36 --- /dev/null +++ b/tests/ui/move-expr/name-resolution.rs @@ -0,0 +1,13 @@ +//@ check-pass +//@ ignore-test (#155050): currently ICEs instead of reporting a name-resolution error +// FIXME(TaKO8Ki): Remove this ignore once closure-local names in `move(expr)` produce a real +// diagnostic instead of hitting the current `Res::Err` ICE path. +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let _c = || { + let x = 3; + move(x); + }; +} diff --git a/tests/ui/move-expr/nested-closures.rs b/tests/ui/move-expr/nested-closures.rs new file mode 100644 index 0000000000000..b472dca4c0024 --- /dev/null +++ b/tests/ui/move-expr/nested-closures.rs @@ -0,0 +1,15 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = String::from("hello"); + let outer = || { + let inner = || move(x.clone()); + let y = inner(); + assert_eq!(y, "hello"); + assert_eq!(x, "hello"); + }; + + outer(); +} diff --git a/tests/ui/move-expr/outside-plain-closure.rs b/tests/ui/move-expr/outside-plain-closure.rs new file mode 100644 index 0000000000000..c4aa6551119fe --- /dev/null +++ b/tests/ui/move-expr/outside-plain-closure.rs @@ -0,0 +1,7 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let _ = move(String::from("nope")); + //~^ ERROR `move(expr)` is only supported in plain closures +} diff --git a/tests/ui/move-expr/outside-plain-closure.stderr b/tests/ui/move-expr/outside-plain-closure.stderr new file mode 100644 index 0000000000000..68c4223641304 --- /dev/null +++ b/tests/ui/move-expr/outside-plain-closure.stderr @@ -0,0 +1,8 @@ +error: `move(expr)` is only supported in plain closures + --> $DIR/outside-plain-closure.rs:5:13 + | +LL | let _ = move(String::from("nope")); + | ^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/move-expr/parse-ambiguity-errors.rs b/tests/ui/move-expr/parse-ambiguity-errors.rs new file mode 100644 index 0000000000000..c2927373cb8a7 --- /dev/null +++ b/tests/ui/move-expr/parse-ambiguity-errors.rs @@ -0,0 +1,14 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x: bool = true; + let y: bool = true; + let _ = move(x) || y; + //~^ ERROR `move(expr)` is only supported in plain closures + + let x: bool = true; + let y: bool = true; + let _ = move[x] || y; + //~^ ERROR expected one of +} diff --git a/tests/ui/move-expr/parse-ambiguity-errors.stderr b/tests/ui/move-expr/parse-ambiguity-errors.stderr new file mode 100644 index 0000000000000..c4dc929eac36c --- /dev/null +++ b/tests/ui/move-expr/parse-ambiguity-errors.stderr @@ -0,0 +1,14 @@ +error: expected one of `async`, `|`, or `||`, found `[` + --> $DIR/parse-ambiguity-errors.rs:12:17 + | +LL | let _ = move[x] || y; + | ^ expected one of `async`, `|`, or `||` + +error: `move(expr)` is only supported in plain closures + --> $DIR/parse-ambiguity-errors.rs:7:13 + | +LL | let _ = move(x) || y; + | ^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/move-expr/parse-ambiguity.rs b/tests/ui/move-expr/parse-ambiguity.rs new file mode 100644 index 0000000000000..bfe4b82b2b0ff --- /dev/null +++ b/tests/ui/move-expr/parse-ambiguity.rs @@ -0,0 +1,13 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x: bool = true; + let y: bool = true; + let _ = || move(x) || y; + + let x: bool = true; + let y: bool = true; + let _ = move || move(x) || y; +} diff --git a/tests/ui/move-expr/plain-closure.rs b/tests/ui/move-expr/plain-closure.rs new file mode 100644 index 0000000000000..dc3e3b2956a15 --- /dev/null +++ b/tests/ui/move-expr/plain-closure.rs @@ -0,0 +1,29 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = String::from("hello"); + let c = || { + let t = move(s); + println!("{}", t.len()); + }; + c(); + + let a = String::from("hello"); + let b = String::from("world"); + let c = || { + let x = move(a); + let y = move(b); + println!("{} {}", x, y); + }; + c(); + + let v = "Hello, Ferris".to_string(); + let r = || { + || { + (move(move(v.clone()))).len() + } + }; + assert_eq!(r()(), v.len()); +} diff --git a/tests/ui/move-expr/use-after-move.rs b/tests/ui/move-expr/use-after-move.rs new file mode 100644 index 0000000000000..69f2bf35125df --- /dev/null +++ b/tests/ui/move-expr/use-after-move.rs @@ -0,0 +1,12 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = vec![1, 2, 3]; + let _c = || { + //~^ ERROR borrow of moved value: `x` + let y = move(x); + println!("{x:?}"); + drop(y); + }; +} diff --git a/tests/ui/move-expr/use-after-move.stderr b/tests/ui/move-expr/use-after-move.stderr new file mode 100644 index 0000000000000..4b97f812815c0 --- /dev/null +++ b/tests/ui/move-expr/use-after-move.stderr @@ -0,0 +1,25 @@ +error[E0382]: borrow of moved value: `x` + --> $DIR/use-after-move.rs:6:14 + | +LL | let x = vec![1, 2, 3]; + | - move occurs because `x` has type `Vec`, which does not implement the `Copy` trait +LL | let _c = || { + | ^^ value borrowed here after move +LL | +LL | let y = move(x); + | - value moved here +LL | println!("{x:?}"); + | - borrow occurs due to use in closure + | +help: consider cloning the value if the performance cost is acceptable + | +LL | let y = move(x.clone()); + | ++++++++ +help: borrow this binding in the pattern to avoid moving the value + | +LL | let y = move(ref x); + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`.