@@ -14,6 +14,7 @@ mod clone_on_copy;
1414mod clone_on_ref_ptr;
1515mod cloned_instead_of_copied;
1616mod collapsible_str_replace;
17+ mod double_ended_iterator_last;
1718mod drain_collect;
1819mod err_expect;
1920mod expect_fun_call;
@@ -4284,6 +4285,32 @@ declare_clippy_lint! {
42844285 "map of a trivial closure (not dependent on parameter) over a range"
42854286}
42864287
4288+ declare_clippy_lint ! {
4289+ /// ### What it does
4290+ ///
4291+ /// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced
4292+ /// with `DoubleEndedIterator::next_back`.
4293+ ///
4294+ /// ### Why is this bad?
4295+ ///
4296+ /// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if
4297+ /// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization,
4298+ /// `Iterator::last` cannot be optimized for `DoubleEndedIterator`.
4299+ ///
4300+ /// ### Example
4301+ /// ```no_run
4302+ /// let last_arg = "echo hello world".split(' ').last();
4303+ /// ```
4304+ /// Use instead:
4305+ /// ```no_run
4306+ /// let last_arg = "echo hello world".split(' ').next_back();
4307+ /// ```
4308+ #[ clippy:: version = "1.85.0" ]
4309+ pub DOUBLE_ENDED_ITERATOR_LAST ,
4310+ perf,
4311+ "using `Iterator::last` on a `DoubleEndedIterator`"
4312+ }
4313+
42874314pub struct Methods {
42884315 avoid_breaking_exported_api : bool ,
42894316 msrv : Msrv ,
@@ -4449,6 +4476,7 @@ impl_lint_pass!(Methods => [
44494476 MAP_ALL_ANY_IDENTITY ,
44504477 MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES ,
44514478 UNNECESSARY_MAP_OR ,
4479+ DOUBLE_ENDED_ITERATOR_LAST ,
44524480] ) ;
44534481
44544482/// Extracts a method call name, args, and `Span` of the method name.
@@ -4931,6 +4959,7 @@ impl Methods {
49314959 false ,
49324960 ) ;
49334961 }
4962+ double_ended_iterator_last:: check ( cx, expr, recv, call_span) ;
49344963 } ,
49354964 ( "len" , [ ] ) => {
49364965 if let Some ( ( "as_bytes" , prev_recv, [ ] , _, _) ) = method_call ( recv) {
0 commit comments