mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 08:13:41 +00:00
Suggest borrowing arguments in generic positions when trait bounds are satisfied
This subsumes the suggestions to borrow arguments with `AsRef`/`Borrow` bounds and those to borrow arguments with `Fn` and `FnMut` bounds. It works for other traits implemented on references as well, such as `std::io::Read`, `std::io::Write`, and `core::fmt::Write`. Incidentally, by making the logic for suggesting borrowing closures general, this removes some spurious suggestions to mutably borrow `FnMut` closures in assignments, as well as an unhelpful suggestion to add a `Clone` constraint to an `impl Fn` argument.
This commit is contained in:
parent
ea37000b56
commit
2ab8480605
@ -27,7 +27,7 @@ use rustc_middle::mir::{
|
|||||||
};
|
};
|
||||||
use rustc_middle::ty::print::PrintTraitRefExt as _;
|
use rustc_middle::ty::print::PrintTraitRefExt as _;
|
||||||
use rustc_middle::ty::{
|
use rustc_middle::ty::{
|
||||||
self, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,
|
self, ClauseKind, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,
|
||||||
suggest_constraining_type_params,
|
suggest_constraining_type_params,
|
||||||
};
|
};
|
||||||
use rustc_middle::util::CallKind;
|
use rustc_middle::util::CallKind;
|
||||||
@ -39,6 +39,7 @@ use rustc_span::{BytePos, Span, Symbol};
|
|||||||
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
|
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
|
||||||
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
|
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
|
||||||
use rustc_trait_selection::infer::InferCtxtExt;
|
use rustc_trait_selection::infer::InferCtxtExt;
|
||||||
|
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||||
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
|
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
|
||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
@ -201,16 +202,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
|
|
||||||
let mut has_suggest_reborrow = false;
|
let mut has_suggest_reborrow = false;
|
||||||
if !seen_spans.contains(&move_span) {
|
if !seen_spans.contains(&move_span) {
|
||||||
if !closure {
|
self.suggest_ref_or_clone(
|
||||||
self.suggest_ref_or_clone(
|
mpi,
|
||||||
mpi,
|
&mut err,
|
||||||
&mut err,
|
&mut in_pattern,
|
||||||
&mut in_pattern,
|
move_spans,
|
||||||
move_spans,
|
moved_place.as_ref(),
|
||||||
moved_place.as_ref(),
|
&mut has_suggest_reborrow,
|
||||||
&mut has_suggest_reborrow,
|
closure,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let msg_opt = CapturedMessageOpt {
|
let msg_opt = CapturedMessageOpt {
|
||||||
is_partial_move,
|
is_partial_move,
|
||||||
@ -266,27 +266,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let opt_name = self.describe_place_with_options(place.as_ref(), DescribePlaceOpt {
|
if self.param_env.caller_bounds().iter().any(|c| {
|
||||||
including_downcast: true,
|
c.as_trait_clause().is_some_and(|pred| {
|
||||||
including_tuple_field: true,
|
pred.skip_binder().self_ty() == ty && self.infcx.tcx.is_fn_trait(pred.def_id())
|
||||||
});
|
})
|
||||||
let note_msg = match opt_name {
|
}) {
|
||||||
Some(name) => format!("`{name}`"),
|
|
||||||
None => "value".to_owned(),
|
|
||||||
};
|
|
||||||
if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, ¬e_msg)
|
|
||||||
|| if let UseSpans::FnSelfUse { kind, .. } = use_spans
|
|
||||||
&& let CallKind::FnCall { fn_trait_id, self_ty } = kind
|
|
||||||
&& let ty::Param(_) = self_ty.kind()
|
|
||||||
&& ty == self_ty
|
|
||||||
&& self.infcx.tcx.is_lang_item(fn_trait_id, LangItem::FnOnce)
|
|
||||||
{
|
|
||||||
// this is a type parameter `T: FnOnce()`, don't suggest `T: FnOnce() + Clone`.
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Suppress the next suggestion since we don't want to put more bounds onto
|
// Suppress the next suggestion since we don't want to put more bounds onto
|
||||||
// something that already has `Fn`-like bounds (or is a closure), so we can't
|
// something that already has `Fn`-like bounds (or is a closure), so we can't
|
||||||
// restrict anyways.
|
// restrict anyways.
|
||||||
@ -295,6 +279,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
self.suggest_adding_bounds(&mut err, ty, copy_did, span);
|
self.suggest_adding_bounds(&mut err, ty, copy_did, span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let opt_name = self.describe_place_with_options(place.as_ref(), DescribePlaceOpt {
|
||||||
|
including_downcast: true,
|
||||||
|
including_tuple_field: true,
|
||||||
|
});
|
||||||
|
let note_msg = match opt_name {
|
||||||
|
Some(name) => format!("`{name}`"),
|
||||||
|
None => "value".to_owned(),
|
||||||
|
};
|
||||||
if needs_note {
|
if needs_note {
|
||||||
if let Some(local) = place.as_local() {
|
if let Some(local) = place.as_local() {
|
||||||
let span = self.body.local_decls[local].source_info.span;
|
let span = self.body.local_decls[local].source_info.span;
|
||||||
@ -341,6 +333,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
move_spans: UseSpans<'tcx>,
|
move_spans: UseSpans<'tcx>,
|
||||||
moved_place: PlaceRef<'tcx>,
|
moved_place: PlaceRef<'tcx>,
|
||||||
has_suggest_reborrow: &mut bool,
|
has_suggest_reborrow: &mut bool,
|
||||||
|
moved_or_invoked_closure: bool,
|
||||||
) {
|
) {
|
||||||
let move_span = match move_spans {
|
let move_span = match move_spans {
|
||||||
UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span,
|
UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span,
|
||||||
@ -428,17 +421,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
}
|
}
|
||||||
let typeck = self.infcx.tcx.typeck(self.mir_def_id());
|
let typeck = self.infcx.tcx.typeck(self.mir_def_id());
|
||||||
let parent = self.infcx.tcx.parent_hir_node(expr.hir_id);
|
let parent = self.infcx.tcx.parent_hir_node(expr.hir_id);
|
||||||
let (def_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent
|
let (def_id, call_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent
|
||||||
&& let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
|
&& let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
|
||||||
{
|
{
|
||||||
(typeck.type_dependent_def_id(parent_expr.hir_id), args, 1)
|
let def_id = typeck.type_dependent_def_id(parent_expr.hir_id);
|
||||||
|
(def_id, Some(parent_expr.hir_id), args, 1)
|
||||||
} else if let hir::Node::Expr(parent_expr) = parent
|
} else if let hir::Node::Expr(parent_expr) = parent
|
||||||
&& let hir::ExprKind::Call(call, args) = parent_expr.kind
|
&& let hir::ExprKind::Call(call, args) = parent_expr.kind
|
||||||
&& let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind()
|
&& let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind()
|
||||||
{
|
{
|
||||||
(Some(*def_id), args, 0)
|
(Some(*def_id), Some(call.hir_id), args, 0)
|
||||||
} else {
|
} else {
|
||||||
(None, &[][..], 0)
|
(None, None, &[][..], 0)
|
||||||
};
|
};
|
||||||
let ty = place.ty(self.body, self.infcx.tcx).ty;
|
let ty = place.ty(self.body, self.infcx.tcx).ty;
|
||||||
|
|
||||||
@ -449,14 +443,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
// The move occurred as one of the arguments to a function call. Is that
|
// The move occurred as one of the arguments to a function call. Is that
|
||||||
// argument generic? `def_id` can't be a closure here, so using `fn_sig` is fine
|
// argument generic? `def_id` can't be a closure here, so using `fn_sig` is fine
|
||||||
let arg_param = if self.infcx.tcx.def_kind(def_id).is_fn_like()
|
let arg_param = if self.infcx.tcx.def_kind(def_id).is_fn_like()
|
||||||
&& let Some(arg_ty) = self
|
&& let sig =
|
||||||
.infcx
|
self.infcx.tcx.fn_sig(def_id).instantiate_identity().skip_binder()
|
||||||
.tcx
|
&& let Some(arg_ty) = sig.inputs().get(pos + offset)
|
||||||
.fn_sig(def_id)
|
|
||||||
.instantiate_identity()
|
|
||||||
.skip_binder()
|
|
||||||
.inputs()
|
|
||||||
.get(pos + offset)
|
|
||||||
&& let ty::Param(arg_param) = arg_ty.kind()
|
&& let ty::Param(arg_param) = arg_ty.kind()
|
||||||
{
|
{
|
||||||
Some(arg_param)
|
Some(arg_param)
|
||||||
@ -478,54 +467,22 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mutbl = ty::Mutability::Not;
|
// If the moved place is used generically by the callee and a reference to it
|
||||||
if let Some(param) = arg_param
|
// would still satisfy any bounds on its type, suggest borrowing.
|
||||||
&& self
|
if let Some(¶m) = arg_param
|
||||||
.infcx
|
&& let Some(generic_args) = call_id.and_then(|id| typeck.node_args_opt(id))
|
||||||
.tcx
|
&& let Some(ref_mutability) = self.suggest_borrow_generic_arg(
|
||||||
.predicates_of(def_id)
|
err,
|
||||||
.instantiate_identity(self.infcx.tcx)
|
def_id,
|
||||||
.predicates
|
generic_args,
|
||||||
.into_iter()
|
param,
|
||||||
.any(|pred| {
|
moved_place,
|
||||||
if let ty::ClauseKind::Trait(predicate) = pred.kind().skip_binder()
|
pos + offset,
|
||||||
&& [
|
ty,
|
||||||
self.infcx.tcx.get_diagnostic_item(sym::AsRef),
|
expr.span,
|
||||||
self.infcx.tcx.get_diagnostic_item(sym::AsMut),
|
)
|
||||||
self.infcx.tcx.get_diagnostic_item(sym::Borrow),
|
|
||||||
self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
|
|
||||||
]
|
|
||||||
.contains(&Some(predicate.def_id()))
|
|
||||||
&& *predicate.self_ty().kind() == ty::Param(*param)
|
|
||||||
{
|
|
||||||
if [
|
|
||||||
self.infcx.tcx.get_diagnostic_item(sym::AsMut),
|
|
||||||
self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
|
|
||||||
]
|
|
||||||
.contains(&Some(predicate.def_id()))
|
|
||||||
{
|
|
||||||
mutbl = ty::Mutability::Mut;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
// The type of the argument corresponding to the expression that got moved
|
can_suggest_clone = ref_mutability.is_mut();
|
||||||
// is a type parameter `T`, which is has a `T: AsRef` obligation.
|
|
||||||
let place_desc = if let Some(desc) = self.describe_place(moved_place) {
|
|
||||||
format!("`{desc}`")
|
|
||||||
} else {
|
|
||||||
"here".to_owned()
|
|
||||||
};
|
|
||||||
err.span_suggestion_verbose(
|
|
||||||
expr.span.shrink_to_lo(),
|
|
||||||
format!("consider {}borrowing {place_desc}", mutbl.mutably_str()),
|
|
||||||
mutbl.ref_prefix_str(),
|
|
||||||
Applicability::MaybeIncorrect,
|
|
||||||
);
|
|
||||||
can_suggest_clone = mutbl.is_mut();
|
|
||||||
} else if let Some(local_def_id) = def_id.as_local()
|
} else if let Some(local_def_id) = def_id.as_local()
|
||||||
&& let node = self.infcx.tcx.hir_node_by_def_id(local_def_id)
|
&& let node = self.infcx.tcx.hir_node_by_def_id(local_def_id)
|
||||||
&& let Some(fn_decl) = node.fn_decl()
|
&& let Some(fn_decl) = node.fn_decl()
|
||||||
@ -563,6 +520,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
} else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans
|
} else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans
|
||||||
{
|
{
|
||||||
// We already suggest cloning for these cases in `explain_captures`.
|
// We already suggest cloning for these cases in `explain_captures`.
|
||||||
|
} else if moved_or_invoked_closure {
|
||||||
|
// Do not suggest `closure.clone()()`.
|
||||||
} else if let UseSpans::ClosureUse {
|
} else if let UseSpans::ClosureUse {
|
||||||
closure_kind:
|
closure_kind:
|
||||||
ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)),
|
ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)),
|
||||||
@ -671,6 +630,113 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If a place is used after being moved as an argument to a function, the function is generic
|
||||||
|
/// in that argument, and a reference to the argument's type would still satisfy the function's
|
||||||
|
/// bounds, suggest borrowing. This covers, e.g., borrowing an `impl Fn()` argument being passed
|
||||||
|
/// in an `impl FnOnce()` position.
|
||||||
|
/// Returns `Some(mutability)` when suggesting to borrow with mutability `mutability`, or `None`
|
||||||
|
/// if no suggestion is made.
|
||||||
|
fn suggest_borrow_generic_arg(
|
||||||
|
&self,
|
||||||
|
err: &mut Diag<'_>,
|
||||||
|
callee_did: DefId,
|
||||||
|
generic_args: ty::GenericArgsRef<'tcx>,
|
||||||
|
param: ty::ParamTy,
|
||||||
|
moved_place: PlaceRef<'tcx>,
|
||||||
|
moved_arg_pos: usize,
|
||||||
|
moved_arg_ty: Ty<'tcx>,
|
||||||
|
place_span: Span,
|
||||||
|
) -> Option<ty::Mutability> {
|
||||||
|
let tcx = self.infcx.tcx;
|
||||||
|
let sig = tcx.fn_sig(callee_did).instantiate_identity().skip_binder();
|
||||||
|
let clauses = tcx.predicates_of(callee_did).instantiate_identity(self.infcx.tcx).predicates;
|
||||||
|
|
||||||
|
// First, is there at least one method on one of `param`'s trait bounds?
|
||||||
|
// This keeps us from suggesting borrowing the argument to `mem::drop`, e.g.
|
||||||
|
if !clauses.iter().any(|clause| {
|
||||||
|
clause.as_trait_clause().is_some_and(|tc| {
|
||||||
|
tc.self_ty().skip_binder().is_param(param.index)
|
||||||
|
&& tc.polarity() == ty::PredicatePolarity::Positive
|
||||||
|
&& tcx
|
||||||
|
.supertrait_def_ids(tc.def_id())
|
||||||
|
.flat_map(|trait_did| tcx.associated_items(trait_did).in_definition_order())
|
||||||
|
.any(|item| item.fn_has_self_parameter)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try borrowing a shared reference first, then mutably.
|
||||||
|
if let Some(mutbl) = [ty::Mutability::Not, ty::Mutability::Mut].into_iter().find(|&mutbl| {
|
||||||
|
let re = self.infcx.tcx.lifetimes.re_erased;
|
||||||
|
let ref_ty = Ty::new_ref(self.infcx.tcx, re, moved_arg_ty, mutbl);
|
||||||
|
|
||||||
|
// Ensure that substituting `ref_ty` in the callee's signature doesn't break
|
||||||
|
// other inputs or the return type.
|
||||||
|
let new_args = tcx.mk_args_from_iter(generic_args.iter().enumerate().map(
|
||||||
|
|(i, arg)| {
|
||||||
|
if i == param.index as usize { ref_ty.into() } else { arg }
|
||||||
|
},
|
||||||
|
));
|
||||||
|
let can_subst = |ty: Ty<'tcx>| {
|
||||||
|
// Normalize before comparing to see through type aliases and projections.
|
||||||
|
let old_ty = ty::EarlyBinder::bind(ty).instantiate(tcx, generic_args);
|
||||||
|
let new_ty = ty::EarlyBinder::bind(ty).instantiate(tcx, new_args);
|
||||||
|
if let Ok(old_ty) = tcx.try_normalize_erasing_regions(self.param_env, old_ty)
|
||||||
|
&& let Ok(new_ty) = tcx.try_normalize_erasing_regions(self.param_env, new_ty)
|
||||||
|
{
|
||||||
|
old_ty == new_ty
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !can_subst(sig.output())
|
||||||
|
|| sig
|
||||||
|
.inputs()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.any(|(i, &input_ty)| i != moved_arg_pos && !can_subst(input_ty))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the callee's predicates, substituting a reference in for the self ty
|
||||||
|
// in bounds on `param`.
|
||||||
|
clauses.iter().all(|&clause| {
|
||||||
|
let clause_for_ref = clause.kind().map_bound(|kind| match kind {
|
||||||
|
ClauseKind::Trait(c) if c.self_ty().is_param(param.index) => {
|
||||||
|
ClauseKind::Trait(c.with_self_ty(tcx, ref_ty))
|
||||||
|
}
|
||||||
|
ClauseKind::Projection(c) if c.self_ty().is_param(param.index) => {
|
||||||
|
ClauseKind::Projection(c.with_self_ty(tcx, ref_ty))
|
||||||
|
}
|
||||||
|
_ => kind,
|
||||||
|
});
|
||||||
|
self.infcx.predicate_must_hold_modulo_regions(&Obligation::new(
|
||||||
|
tcx,
|
||||||
|
ObligationCause::dummy(),
|
||||||
|
self.param_env,
|
||||||
|
ty::EarlyBinder::bind(clause_for_ref).instantiate(tcx, generic_args),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
let place_desc = if let Some(desc) = self.describe_place(moved_place) {
|
||||||
|
format!("`{desc}`")
|
||||||
|
} else {
|
||||||
|
"here".to_owned()
|
||||||
|
};
|
||||||
|
err.span_suggestion_verbose(
|
||||||
|
place_span.shrink_to_lo(),
|
||||||
|
format!("consider {}borrowing {place_desc}", mutbl.mutably_str()),
|
||||||
|
mutbl.ref_prefix_str(),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
Some(mutbl)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn report_use_of_uninitialized(
|
fn report_use_of_uninitialized(
|
||||||
&self,
|
&self,
|
||||||
mpi: MovePathIndex,
|
mpi: MovePathIndex,
|
||||||
@ -851,74 +917,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suggest_borrow_fn_like(
|
|
||||||
&self,
|
|
||||||
err: &mut Diag<'_>,
|
|
||||||
ty: Ty<'tcx>,
|
|
||||||
move_sites: &[MoveSite],
|
|
||||||
value_name: &str,
|
|
||||||
) -> bool {
|
|
||||||
let tcx = self.infcx.tcx;
|
|
||||||
|
|
||||||
// Find out if the predicates show that the type is a Fn or FnMut
|
|
||||||
let find_fn_kind_from_did = |(pred, _): (ty::Clause<'tcx>, _)| {
|
|
||||||
if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder()
|
|
||||||
&& pred.self_ty() == ty
|
|
||||||
{
|
|
||||||
if tcx.is_lang_item(pred.def_id(), LangItem::Fn) {
|
|
||||||
return Some(hir::Mutability::Not);
|
|
||||||
} else if tcx.is_lang_item(pred.def_id(), LangItem::FnMut) {
|
|
||||||
return Some(hir::Mutability::Mut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
|
|
||||||
// borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
|
|
||||||
// These types seem reasonably opaque enough that they could be instantiated with their
|
|
||||||
// borrowed variants in a function body when we see a move error.
|
|
||||||
let borrow_level = match *ty.kind() {
|
|
||||||
ty::Param(_) => tcx
|
|
||||||
.explicit_predicates_of(self.mir_def_id().to_def_id())
|
|
||||||
.predicates
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.find_map(find_fn_kind_from_did),
|
|
||||||
ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => tcx
|
|
||||||
.explicit_item_super_predicates(def_id)
|
|
||||||
.iter_instantiated_copied(tcx, args)
|
|
||||||
.find_map(|(clause, span)| find_fn_kind_from_did((clause, span))),
|
|
||||||
ty::Closure(_, args) => match args.as_closure().kind() {
|
|
||||||
ty::ClosureKind::Fn => Some(hir::Mutability::Not),
|
|
||||||
ty::ClosureKind::FnMut => Some(hir::Mutability::Mut),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(borrow_level) = borrow_level else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let sugg = move_sites
|
|
||||||
.iter()
|
|
||||||
.map(|move_site| {
|
|
||||||
let move_out = self.move_data.moves[(*move_site).moi];
|
|
||||||
let moved_place = &self.move_data.move_paths[move_out.path].place;
|
|
||||||
let move_spans = self.move_spans(moved_place.as_ref(), move_out.source);
|
|
||||||
let move_span = move_spans.args_or_use();
|
|
||||||
let suggestion = borrow_level.ref_prefix_str().to_owned();
|
|
||||||
(move_span.shrink_to_lo(), suggestion)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
err.multipart_suggestion_verbose(
|
|
||||||
format!("consider {}borrowing {value_name}", borrow_level.mutably_str()),
|
|
||||||
sugg,
|
|
||||||
Applicability::MaybeIncorrect,
|
|
||||||
);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// In a move error that occurs on a call within a loop, we try to identify cases where cloning
|
/// In a move error that occurs on a call within a loop, we try to identify cases where cloning
|
||||||
/// the value would lead to a logic error. We infer these cases by seeing if the moved value is
|
/// the value would lead to a logic error. We infer these cases by seeing if the moved value is
|
||||||
/// part of the logic to break the loop, either through an explicit `break` or if the expression
|
/// part of the logic to break the loop, either through an explicit `break` or if the expression
|
||||||
|
@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
|
|||||||
|
|
|
|
||||||
LL | if let MultiVariant::Point(ref mut x, _) = point {
|
LL | if let MultiVariant::Point(ref mut x, _) = point {
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
help: consider mutably borrowing `c`
|
|
||||||
|
|
|
||||||
LL | let a = &mut c;
|
|
||||||
| ++++
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
|
|||||||
|
|
|
|
||||||
LL | let SingleVariant::Point(ref mut x, _) = point;
|
LL | let SingleVariant::Point(ref mut x, _) = point;
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
help: consider mutably borrowing `c`
|
|
||||||
|
|
|
||||||
LL | let b = &mut c;
|
|
||||||
| ++++
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
|
|||||||
|
|
|
|
||||||
LL | x.y.a += 1;
|
LL | x.y.a += 1;
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
help: consider mutably borrowing `hello`
|
|
||||||
|
|
|
||||||
LL | let b = &mut hello;
|
|
||||||
| ++++
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
|
|||||||
|
|
|
|
||||||
LL | x.0 += 1;
|
LL | x.0 += 1;
|
||||||
| ^^^
|
| ^^^
|
||||||
help: consider mutably borrowing `hello`
|
|
||||||
|
|
|
||||||
LL | let b = &mut hello;
|
|
||||||
| ++++
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
@ -1,23 +1,20 @@
|
|||||||
//! auxiliary definitons for suggest-borrow-for-generic-arg.rs, to ensure the suggestion works on
|
//! auxiliary definitons for suggest-borrow-for-generic-arg.rs, to ensure the suggestion works on
|
||||||
//! functions defined in other crates.
|
//! functions defined in other crates.
|
||||||
|
|
||||||
use std::borrow::{Borrow, BorrowMut};
|
use std::io::{self, Read, Write};
|
||||||
use std::convert::{AsMut, AsRef};
|
use std::iter::Sum;
|
||||||
pub struct Bar;
|
|
||||||
|
|
||||||
impl AsRef<Bar> for Bar {
|
pub fn write_stuff<W: Write>(mut writer: W) -> io::Result<()> {
|
||||||
fn as_ref(&self) -> &Bar {
|
writeln!(writer, "stuff")
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsMut<Bar> for Bar {
|
pub fn read_and_discard<R: Read>(mut reader: R) -> io::Result<()> {
|
||||||
fn as_mut(&mut self) -> &mut Bar {
|
let mut buf = Vec::new();
|
||||||
self
|
reader.read_to_end(&mut buf).map(|_| ())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn foo<T: AsRef<Bar>>(_: T) {}
|
pub fn sum_three<I: IntoIterator>(iter: I) -> <I as IntoIterator>::Item
|
||||||
pub fn qux<T: AsMut<Bar>>(_: T) {}
|
where <I as IntoIterator>::Item: Sum
|
||||||
pub fn bat<T: Borrow<T>>(_: T) {}
|
{
|
||||||
pub fn baz<T: BorrowMut<T>>(_: T) {}
|
iter.into_iter().take(3).sum()
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
fn takes_fn(f: impl Fn()) { //~ HELP if `impl Fn()` implemented `Clone`
|
fn takes_fn(f: impl Fn()) {
|
||||||
loop {
|
loop {
|
||||||
takes_fnonce(f);
|
takes_fnonce(f);
|
||||||
//~^ ERROR use of moved value
|
//~^ ERROR use of moved value
|
||||||
|
@ -8,21 +8,6 @@ LL | loop {
|
|||||||
LL | takes_fnonce(f);
|
LL | takes_fnonce(f);
|
||||||
| ^ value moved here, in previous iteration of loop
|
| ^ value moved here, in previous iteration of loop
|
||||||
|
|
|
|
||||||
note: consider changing this parameter type in function `takes_fnonce` to borrow instead if owning the value isn't necessary
|
|
||||||
--> $DIR/borrow-closures-instead-of-move.rs:34:20
|
|
||||||
|
|
|
||||||
LL | fn takes_fnonce(_: impl FnOnce()) {}
|
|
||||||
| ------------ ^^^^^^^^^^^^^ this parameter takes ownership of the value
|
|
||||||
| |
|
|
||||||
| in this function
|
|
||||||
help: if `impl Fn()` implemented `Clone`, you could clone the value
|
|
||||||
--> $DIR/borrow-closures-instead-of-move.rs:1:16
|
|
||||||
|
|
|
||||||
LL | fn takes_fn(f: impl Fn()) {
|
|
||||||
| ^^^^^^^^^ consider constraining this type parameter with `Clone`
|
|
||||||
LL | loop {
|
|
||||||
LL | takes_fnonce(f);
|
|
||||||
| - you could clone this value
|
|
||||||
help: consider borrowing `f`
|
help: consider borrowing `f`
|
||||||
|
|
|
|
||||||
LL | takes_fnonce(&f);
|
LL | takes_fnonce(&f);
|
||||||
@ -40,13 +25,6 @@ LL | takes_fnonce(m);
|
|||||||
LL | takes_fnonce(m);
|
LL | takes_fnonce(m);
|
||||||
| ^ value used here after move
|
| ^ value used here after move
|
||||||
|
|
|
|
||||||
note: consider changing this parameter type in function `takes_fnonce` to borrow instead if owning the value isn't necessary
|
|
||||||
--> $DIR/borrow-closures-instead-of-move.rs:34:20
|
|
||||||
|
|
|
||||||
LL | fn takes_fnonce(_: impl FnOnce()) {}
|
|
||||||
| ------------ ^^^^^^^^^^^^^ this parameter takes ownership of the value
|
|
||||||
| |
|
|
||||||
| in this function
|
|
||||||
help: if `impl FnMut()` implemented `Clone`, you could clone the value
|
help: if `impl FnMut()` implemented `Clone`, you could clone the value
|
||||||
--> $DIR/borrow-closures-instead-of-move.rs:9:20
|
--> $DIR/borrow-closures-instead-of-move.rs:9:20
|
||||||
|
|
|
|
||||||
|
@ -24,10 +24,6 @@ LL | fn conspirator<F>(mut f: F) where F: FnMut(&mut R, bool) {
|
|||||||
| ^ consider constraining this type parameter with `Clone`
|
| ^ consider constraining this type parameter with `Clone`
|
||||||
LL | let mut r = R {c: Box::new(f)};
|
LL | let mut r = R {c: Box::new(f)};
|
||||||
| - you could clone this value
|
| - you could clone this value
|
||||||
help: consider mutably borrowing `f`
|
|
||||||
|
|
|
||||||
LL | let mut r = R {c: Box::new(&mut f)};
|
|
||||||
| ++++
|
|
||||||
|
|
||||||
error: aborting due to 2 previous errors
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
@ -1,22 +1,46 @@
|
|||||||
//! Test suggetions to borrow generic arguments instead of moving when all predicates hold after
|
//! Test suggetions to borrow generic arguments instead of moving. Tests for other instances of this
|
||||||
//! substituting in the reference type.
|
//! can be found in `moved-value-on-as-ref-arg.rs` and `borrow-closures-instead-of-move.rs`
|
||||||
//@ run-rustfix
|
//@ run-rustfix
|
||||||
//@ aux-build:suggest-borrow-for-generic-arg-aux.rs
|
//@ aux-crate:aux=suggest-borrow-for-generic-arg-aux.rs
|
||||||
|
//@ edition: 2021
|
||||||
|
|
||||||
#![allow(unused_mut)]
|
#![allow(unused_mut)]
|
||||||
extern crate suggest_borrow_for_generic_arg_aux as aux;
|
use std::io::{self, Write};
|
||||||
|
|
||||||
pub fn main() {
|
// test for `std::io::Write` (#131413)
|
||||||
let bar = aux::Bar;
|
fn test_write() -> io::Result<()> {
|
||||||
aux::foo(&bar); //~ HELP consider borrowing `bar`
|
let mut stdout = io::stdout();
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
aux::write_stuff(&stdout)?; //~ HELP consider borrowing `stdout`
|
||||||
let mut bar = aux::Bar;
|
writeln!(stdout, "second line")?; //~ ERROR borrow of moved value: `stdout`
|
||||||
aux::qux(&mut bar); //~ HELP consider mutably borrowing `bar`
|
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
let mut buf = Vec::new();
|
||||||
let bar = aux::Bar;
|
aux::write_stuff(&mut buf.clone())?; //~ HELP consider mutably borrowing `buf`
|
||||||
aux::bat(&bar); //~ HELP consider borrowing `bar`
|
//~^ HELP consider cloning the value
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
writeln!(buf, "second_line") //~ ERROR borrow of moved value: `buf`
|
||||||
let mut bar = aux::Bar;
|
}
|
||||||
aux::baz(&mut bar); //~ HELP consider mutably borrowing `bar`
|
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
/// test for `std::io::Read` (#131413)
|
||||||
|
fn test_read() -> io::Result<()> {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
aux::read_and_discard(&stdin)?; //~ HELP consider borrowing `stdin`
|
||||||
|
aux::read_and_discard(stdin)?; //~ ERROR use of moved value: `stdin`
|
||||||
|
|
||||||
|
let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
|
||||||
|
aux::read_and_discard(&mut bytes.clone())?; //~ HELP consider mutably borrowing `bytes`
|
||||||
|
//~^ HELP consider cloning the value
|
||||||
|
aux::read_and_discard(bytes) //~ ERROR use of moved value: `bytes`
|
||||||
|
}
|
||||||
|
|
||||||
|
/// test that suggestions work with projection types in the callee's signature
|
||||||
|
fn test_projections() {
|
||||||
|
let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
|
||||||
|
let _six: usize = aux::sum_three(&mut iter.clone()); //~ HELP consider mutably borrowing `iter`
|
||||||
|
//~^ HELP consider cloning the value
|
||||||
|
let _fifteen: usize = aux::sum_three(iter); //~ ERROR use of moved value: `iter`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test_write().unwrap();
|
||||||
|
test_read().unwrap();
|
||||||
|
test_projections();
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,46 @@
|
|||||||
//! Test suggetions to borrow generic arguments instead of moving when all predicates hold after
|
//! Test suggetions to borrow generic arguments instead of moving. Tests for other instances of this
|
||||||
//! substituting in the reference type.
|
//! can be found in `moved-value-on-as-ref-arg.rs` and `borrow-closures-instead-of-move.rs`
|
||||||
//@ run-rustfix
|
//@ run-rustfix
|
||||||
//@ aux-build:suggest-borrow-for-generic-arg-aux.rs
|
//@ aux-crate:aux=suggest-borrow-for-generic-arg-aux.rs
|
||||||
|
//@ edition: 2021
|
||||||
|
|
||||||
#![allow(unused_mut)]
|
#![allow(unused_mut)]
|
||||||
extern crate suggest_borrow_for_generic_arg_aux as aux;
|
use std::io::{self, Write};
|
||||||
|
|
||||||
pub fn main() {
|
// test for `std::io::Write` (#131413)
|
||||||
let bar = aux::Bar;
|
fn test_write() -> io::Result<()> {
|
||||||
aux::foo(bar); //~ HELP consider borrowing `bar`
|
let mut stdout = io::stdout();
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
aux::write_stuff(stdout)?; //~ HELP consider borrowing `stdout`
|
||||||
let mut bar = aux::Bar;
|
writeln!(stdout, "second line")?; //~ ERROR borrow of moved value: `stdout`
|
||||||
aux::qux(bar); //~ HELP consider mutably borrowing `bar`
|
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
let mut buf = Vec::new();
|
||||||
let bar = aux::Bar;
|
aux::write_stuff(buf)?; //~ HELP consider mutably borrowing `buf`
|
||||||
aux::bat(bar); //~ HELP consider borrowing `bar`
|
//~^ HELP consider cloning the value
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
writeln!(buf, "second_line") //~ ERROR borrow of moved value: `buf`
|
||||||
let mut bar = aux::Bar;
|
}
|
||||||
aux::baz(bar); //~ HELP consider mutably borrowing `bar`
|
|
||||||
let _baa = bar; //~ ERROR use of moved value
|
/// test for `std::io::Read` (#131413)
|
||||||
|
fn test_read() -> io::Result<()> {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
aux::read_and_discard(stdin)?; //~ HELP consider borrowing `stdin`
|
||||||
|
aux::read_and_discard(stdin)?; //~ ERROR use of moved value: `stdin`
|
||||||
|
|
||||||
|
let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
|
||||||
|
aux::read_and_discard(bytes)?; //~ HELP consider mutably borrowing `bytes`
|
||||||
|
//~^ HELP consider cloning the value
|
||||||
|
aux::read_and_discard(bytes) //~ ERROR use of moved value: `bytes`
|
||||||
|
}
|
||||||
|
|
||||||
|
/// test that suggestions work with projection types in the callee's signature
|
||||||
|
fn test_projections() {
|
||||||
|
let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
|
||||||
|
let _six: usize = aux::sum_three(iter); //~ HELP consider mutably borrowing `iter`
|
||||||
|
//~^ HELP consider cloning the value
|
||||||
|
let _fifteen: usize = aux::sum_three(iter); //~ ERROR use of moved value: `iter`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test_write().unwrap();
|
||||||
|
test_read().unwrap();
|
||||||
|
test_projections();
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,93 @@
|
|||||||
error[E0382]: use of moved value: `bar`
|
error[E0382]: borrow of moved value: `stdout`
|
||||||
--> $DIR/suggest-borrow-for-generic-arg.rs:12:16
|
--> $DIR/suggest-borrow-for-generic-arg.rs:14:14
|
||||||
|
|
|
|
||||||
LL | let bar = aux::Bar;
|
LL | let mut stdout = io::stdout();
|
||||||
| --- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait
|
| ---------- move occurs because `stdout` has type `Stdout`, which does not implement the `Copy` trait
|
||||||
LL | aux::foo(bar);
|
LL | aux::write_stuff(stdout)?;
|
||||||
| --- value moved here
|
| ------ value moved here
|
||||||
LL | let _baa = bar;
|
LL | writeln!(stdout, "second line")?;
|
||||||
| ^^^ value used here after move
|
| ^^^^^^ value borrowed here after move
|
||||||
|
|
|
|
||||||
help: consider borrowing `bar`
|
help: consider borrowing `stdout`
|
||||||
|
|
|
|
||||||
LL | aux::foo(&bar);
|
LL | aux::write_stuff(&stdout)?;
|
||||||
| +
|
| +
|
||||||
|
|
||||||
error[E0382]: use of moved value: `bar`
|
error[E0382]: borrow of moved value: `buf`
|
||||||
--> $DIR/suggest-borrow-for-generic-arg.rs:15:16
|
--> $DIR/suggest-borrow-for-generic-arg.rs:19:14
|
||||||
|
|
|
|
||||||
LL | let mut bar = aux::Bar;
|
LL | let mut buf = Vec::new();
|
||||||
| ------- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait
|
| ------- move occurs because `buf` has type `Vec<u8>`, which does not implement the `Copy` trait
|
||||||
LL | aux::qux(bar);
|
LL | aux::write_stuff(buf)?;
|
||||||
| --- value moved here
|
| --- value moved here
|
||||||
LL | let _baa = bar;
|
LL |
|
||||||
| ^^^ value used here after move
|
LL | writeln!(buf, "second_line")
|
||||||
|
| ^^^ value borrowed here after move
|
||||||
|
|
|
|
||||||
help: consider mutably borrowing `bar`
|
help: consider mutably borrowing `buf`
|
||||||
|
|
|
|
||||||
LL | aux::qux(&mut bar);
|
LL | aux::write_stuff(&mut buf)?;
|
||||||
| ++++
|
| ++++
|
||||||
|
help: consider cloning the value if the performance cost is acceptable
|
||||||
|
|
|
||||||
|
LL | aux::write_stuff(buf.clone())?;
|
||||||
|
| ++++++++
|
||||||
|
|
||||||
error[E0382]: use of moved value: `bar`
|
error[E0382]: use of moved value: `stdin`
|
||||||
--> $DIR/suggest-borrow-for-generic-arg.rs:18:16
|
--> $DIR/suggest-borrow-for-generic-arg.rs:26:27
|
||||||
|
|
|
|
||||||
LL | let bar = aux::Bar;
|
LL | let stdin = io::stdin();
|
||||||
| --- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait
|
| ----- move occurs because `stdin` has type `Stdin`, which does not implement the `Copy` trait
|
||||||
LL | aux::bat(bar);
|
LL | aux::read_and_discard(stdin)?;
|
||||||
| --- value moved here
|
| ----- value moved here
|
||||||
LL | let _baa = bar;
|
LL | aux::read_and_discard(stdin)?;
|
||||||
| ^^^ value used here after move
|
| ^^^^^ value used here after move
|
||||||
|
|
|
|
||||||
help: consider borrowing `bar`
|
help: consider borrowing `stdin`
|
||||||
|
|
|
|
||||||
LL | aux::bat(&bar);
|
LL | aux::read_and_discard(&stdin)?;
|
||||||
| +
|
| +
|
||||||
|
|
||||||
error[E0382]: use of moved value: `bar`
|
error[E0382]: use of moved value: `bytes`
|
||||||
--> $DIR/suggest-borrow-for-generic-arg.rs:21:16
|
--> $DIR/suggest-borrow-for-generic-arg.rs:31:27
|
||||||
|
|
|
|
||||||
LL | let mut bar = aux::Bar;
|
LL | let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
|
||||||
| ------- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait
|
| --------- move occurs because `bytes` has type `VecDeque<u8>`, which does not implement the `Copy` trait
|
||||||
LL | aux::baz(bar);
|
LL | aux::read_and_discard(bytes)?;
|
||||||
| --- value moved here
|
| ----- value moved here
|
||||||
LL | let _baa = bar;
|
LL |
|
||||||
| ^^^ value used here after move
|
LL | aux::read_and_discard(bytes)
|
||||||
|
| ^^^^^ value used here after move
|
||||||
|
|
|
|
||||||
help: consider mutably borrowing `bar`
|
help: consider mutably borrowing `bytes`
|
||||||
|
|
|
|
||||||
LL | aux::baz(&mut bar);
|
LL | aux::read_and_discard(&mut bytes)?;
|
||||||
| ++++
|
| ++++
|
||||||
|
help: consider cloning the value if the performance cost is acceptable
|
||||||
|
|
|
||||||
|
LL | aux::read_and_discard(bytes.clone())?;
|
||||||
|
| ++++++++
|
||||||
|
|
||||||
error: aborting due to 4 previous errors
|
error[E0382]: use of moved value: `iter`
|
||||||
|
--> $DIR/suggest-borrow-for-generic-arg.rs:39:42
|
||||||
|
|
|
||||||
|
LL | let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
|
||||||
|
| -------- move occurs because `iter` has type `std::array::IntoIter<usize, 6>`, which does not implement the `Copy` trait
|
||||||
|
LL | let _six: usize = aux::sum_three(iter);
|
||||||
|
| ---- value moved here
|
||||||
|
LL |
|
||||||
|
LL | let _fifteen: usize = aux::sum_three(iter);
|
||||||
|
| ^^^^ value used here after move
|
||||||
|
|
|
||||||
|
help: consider mutably borrowing `iter`
|
||||||
|
|
|
||||||
|
LL | let _six: usize = aux::sum_three(&mut iter);
|
||||||
|
| ++++
|
||||||
|
help: consider cloning the value if the performance cost is acceptable
|
||||||
|
|
|
||||||
|
LL | let _six: usize = aux::sum_three(iter.clone());
|
||||||
|
| ++++++++
|
||||||
|
|
||||||
|
error: aborting due to 5 previous errors
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0382`.
|
For more information about this error, try `rustc --explain E0382`.
|
||||||
|
@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
|
|||||||
|
|
|
|
||||||
LL | a += 1;
|
LL | a += 1;
|
||||||
| ^
|
| ^
|
||||||
help: consider mutably borrowing `hello`
|
|
||||||
|
|
|
||||||
LL | let b = &mut hello;
|
|
||||||
| ++++
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user