diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b89170073b..b9e67d361c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2754,6 +2754,7 @@ Released 2018-09-13
 [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
 [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
 [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
+[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
 [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
 [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
 [`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs
index c444984bc13..a07cd5e5f4e 100644
--- a/clippy_lints/src/bytecount.rs
+++ b/clippy_lints/src/bytecount.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::match_type;
-use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::visitors::is_local_used;
 use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
@@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
                 return;
             };
             if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
-            if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle);
+            if !is_local_used(cx, needle, arg_id);
             then {
                 let haystack = if let ExprKind::MethodCall(path, _, args, _) =
                         filter_recv.kind {
diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs
index a403a9846ba..bd8f9cc7343 100644
--- a/clippy_lints/src/collapsible_match.rs
+++ b/clippy_lints/src/collapsible_match.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::visitors::is_local_used;
 use clippy_utils::{is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq};
 use if_chain::if_chain;
 use rustc_hir::LangItem::OptionNone;
@@ -83,13 +83,12 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
         // the "wild-like" branches must be equal
         if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body);
         // the binding must not be used in the if guard
-        let mut used_visitor = LocalUsedVisitor::new(cx, binding_id);
         if match arm.guard {
             None => true,
-            Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !used_visitor.check_expr(expr),
+            Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !is_local_used(cx, expr, binding_id),
         };
         // ...or anywhere in the inner match
-        if !arms_inner.iter().any(|arm| used_visitor.check_arm(arm));
+        if !arms_inner.iter().any(|arm| is_local_used(cx, arm, binding_id));
         then {
             span_lint_and_then(
                 cx,
diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs
index 13f0d43cf8d..834440e912d 100644
--- a/clippy_lints/src/let_if_seq.rs
+++ b/clippy_lints/src/let_if_seq.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::snippet;
-use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor};
+use clippy_utils::{path_to_local_id, visitors::is_local_used};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
@@ -65,11 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
                 if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
                 if let hir::StmtKind::Expr(if_) = expr.kind;
                 if let hir::ExprKind::If(cond, then, ref else_) = if_.kind;
-                let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id);
-                if !used_visitor.check_expr(cond);
+                if !is_local_used(cx, cond, canonical_id);
                 if let hir::ExprKind::Block(then, _) = then.kind;
                 if let Some(value) = check_assign(cx, canonical_id, &*then);
-                if !used_visitor.check_expr(value);
+                if !is_local_used(cx, value, canonical_id);
                 then {
                     let span = stmt.span.to(if_.span);
 
@@ -148,15 +147,13 @@ fn check_assign<'tcx>(
         if let hir::ExprKind::Assign(var, value, _) = expr.kind;
         if path_to_local_id(var, decl);
         then {
-            let mut v = LocalUsedVisitor::new(cx, decl);
-
-            if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) {
-                return None;
+            if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
+                None
+            } else {
+                Some(value)
             }
-
-            return Some(value);
+        } else {
+            None
         }
     }
-
-    None
 }
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 8372d681078..1fadaf4770a 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -773,6 +773,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         methods::MANUAL_FILTER_MAP,
         methods::MANUAL_FIND_MAP,
         methods::MANUAL_SATURATING_ARITHMETIC,
+        methods::MANUAL_SPLIT_ONCE,
         methods::MANUAL_STR_REPEAT,
         methods::MAP_COLLECT_RESULT_UNIT,
         methods::MAP_FLATTEN,
@@ -1319,6 +1320,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(methods::MANUAL_FILTER_MAP),
         LintId::of(methods::MANUAL_FIND_MAP),
         LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+        LintId::of(methods::MANUAL_SPLIT_ONCE),
         LintId::of(methods::MANUAL_STR_REPEAT),
         LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
         LintId::of(methods::MAP_IDENTITY),
@@ -1617,6 +1619,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(methods::ITER_COUNT),
         LintId::of(methods::MANUAL_FILTER_MAP),
         LintId::of(methods::MANUAL_FIND_MAP),
+        LintId::of(methods::MANUAL_SPLIT_ONCE),
         LintId::of(methods::MAP_IDENTITY),
         LintId::of(methods::OPTION_AS_REF_DEREF),
         LintId::of(methods::OPTION_FILTER_MAP),
diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs
index 82bf49f5b49..68bef2f4c8b 100644
--- a/clippy_lints/src/loops/for_kv_map.rs
+++ b/clippy_lints/src/loops/for_kv_map.rs
@@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
 use clippy_utils::source::snippet;
 use clippy_utils::sugg;
 use clippy_utils::ty::is_type_diagnostic_item;
-use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::visitors::is_local_used;
 use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
 use rustc_lint::LateContext;
 use rustc_middle::ty;
@@ -66,9 +66,7 @@ pub(super) fn check<'tcx>(
 fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
     match *pat {
         PatKind::Wild => true,
-        PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
-            !LocalUsedVisitor::new(cx, id).check_expr(body)
-        },
+        PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
         _ => false,
     }
 }
diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs
index d8153abc965..2af6cfe35b5 100644
--- a/clippy_lints/src/loops/manual_flatten.rs
+++ b/clippy_lints/src/loops/manual_flatten.rs
@@ -1,7 +1,7 @@
 use super::utils::make_iterator_snippet;
 use super::MANUAL_FLATTEN;
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::visitors::is_local_used;
 use clippy_utils::{is_lang_ctor, path_to_local_id};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
@@ -49,7 +49,7 @@ pub(super) fn check<'tcx>(
             let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
             if some_ctor || ok_ctor;
             // Ensure epxr in `if let` is not used afterwards
-            if !LocalUsedVisitor::new(cx, pat_hir_id).check_arm(true_arm);
+            if !is_local_used(cx, true_arm, pat_hir_id);
             then {
                 let if_let_type = if some_ctor { "Some" } else { "Ok" };
                 // Prepare the error message
diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs
index 3810d0dcc05..fa33d068d3d 100644
--- a/clippy_lints/src/loops/needless_range_loop.rs
+++ b/clippy_lints/src/loops/needless_range_loop.rs
@@ -2,10 +2,8 @@ use super::NEEDLESS_RANGE_LOOP;
 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
 use clippy_utils::source::snippet;
 use clippy_utils::ty::has_iter_method;
-use clippy_utils::visitors::LocalUsedVisitor;
-use clippy_utils::{
-    contains_name, higher, is_integer_const, match_trait_method, path_to_local_id, paths, sugg, SpanlessEq,
-};
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
 use if_chain::if_chain;
 use rustc_ast::ast;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -256,43 +254,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
             if let ExprKind::Path(ref seqpath) = seqexpr.kind;
             if let QPath::Resolved(None, seqvar) = *seqpath;
             if seqvar.segments.len() == 1;
-            let index_used_directly = path_to_local_id(idx, self.var);
-            let indexed_indirectly = {
-                let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var);
-                walk_expr(&mut used_visitor, idx);
-                used_visitor.used
-            };
-            if indexed_indirectly || index_used_directly;
+            if is_local_used(self.cx, idx, self.var);
             then {
                 if self.prefer_mutable {
                     self.indexed_mut.insert(seqvar.segments[0].ident.name);
                 }
+                let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
                 let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
                 match res {
                     Res::Local(hir_id) => {
                         let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
                         let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
                         let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
-                        if indexed_indirectly {
-                            self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
-                        }
                         if index_used_directly {
                             self.indexed_directly.insert(
                                 seqvar.segments[0].ident.name,
                                 (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
                             );
+                        } else {
+                            self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
                         }
                         return false;  // no need to walk further *on the variable*
                     }
                     Res::Def(DefKind::Static | DefKind::Const, ..) => {
-                        if indexed_indirectly {
-                            self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
-                        }
                         if index_used_directly {
                             self.indexed_directly.insert(
                                 seqvar.segments[0].ident.name,
                                 (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
                             );
+                        } else {
+                            self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
                         }
                         return false;  // no need to walk further *on the variable*
                     }
diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs
index 5360c02f905..149c9bee9fb 100644
--- a/clippy_lints/src/matches.rs
+++ b/clippy_lints/src/matches.rs
@@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{
 use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
 use clippy_utils::sugg::Sugg;
 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
-use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::visitors::is_local_used;
 use clippy_utils::{
     get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_wild, meets_msrv, msrvs,
     path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks,
@@ -953,9 +953,7 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
                         // Looking for unused bindings (i.e.: `_e`)
                         for pat in inner.iter() {
                             if let PatKind::Binding(_, id, ident, None) = pat.kind {
-                                if ident.as_str().starts_with('_')
-                                    && !LocalUsedVisitor::new(cx, id).check_expr(arm.body)
-                                {
+                                if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
                                     ident_bind_name = (&ident.name.as_str()).to_string();
                                     matching_wild = true;
                                 }
diff --git a/clippy_lints/src/methods/manual_split_once.rs b/clippy_lints/src/methods/manual_split_once.rs
new file mode 100644
index 00000000000..e273186d051
--- /dev/null
+++ b/clippy_lints/src/methods/manual_split_once.rs
@@ -0,0 +1,213 @@
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::{is_diag_item_method, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, adjustment::Adjust};
+use rustc_span::{symbol::sym, Span, SyntaxContext};
+
+use super::MANUAL_SPLIT_ONCE;
+
+pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
+    if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
+        return;
+    }
+
+    let ctxt = expr.span.ctxt();
+    let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id)) {
+        Some(x) => x,
+        None => return,
+    };
+    let (method_name, msg) = if method_name == "splitn" {
+        ("split_once", "manual implementation of `split_once`")
+    } else {
+        ("rsplit_once", "manual implementation of `rsplit_once`")
+    };
+
+    let mut app = Applicability::MachineApplicable;
+    let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+    let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
+
+    match usage.kind {
+        IterUsageKind::NextTuple => {
+            span_lint_and_sugg(
+                cx,
+                MANUAL_SPLIT_ONCE,
+                usage.span,
+                msg,
+                "try this",
+                format!("{}.{}({})", self_snip, method_name, pat_snip),
+                app,
+            );
+        },
+        IterUsageKind::Next => {
+            let self_deref = {
+                let adjust = cx.typeck_results().expr_adjustments(self_arg);
+                if adjust.is_empty() {
+                    String::new()
+                } else if cx.typeck_results().expr_ty(self_arg).is_box()
+                    || adjust
+                        .iter()
+                        .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
+                {
+                    format!("&{}", "*".repeat(adjust.len() - 1))
+                } else {
+                    "*".repeat(adjust.len() - 2)
+                }
+            };
+            let sugg = if usage.unwrap_kind.is_some() {
+                format!(
+                    "{}.{}({}).map_or({}{}, |x| x.0)",
+                    &self_snip, method_name, pat_snip, self_deref, &self_snip
+                )
+            } else {
+                format!(
+                    "Some({}.{}({}).map_or({}{}, |x| x.0))",
+                    &self_snip, method_name, pat_snip, self_deref, &self_snip
+                )
+            };
+
+            span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
+        },
+        IterUsageKind::Second => {
+            let access_str = match usage.unwrap_kind {
+                Some(UnwrapKind::Unwrap) => ".unwrap().1",
+                Some(UnwrapKind::QuestionMark) => "?.1",
+                None => ".map(|x| x.1)",
+            };
+            span_lint_and_sugg(
+                cx,
+                MANUAL_SPLIT_ONCE,
+                usage.span,
+                msg,
+                "try this",
+                format!("{}.{}({}){}", self_snip, method_name, pat_snip, access_str),
+                app,
+            );
+        },
+    }
+}
+
+enum IterUsageKind {
+    Next,
+    Second,
+    NextTuple,
+}
+
+enum UnwrapKind {
+    Unwrap,
+    QuestionMark,
+}
+
+struct IterUsage {
+    kind: IterUsageKind,
+    unwrap_kind: Option<UnwrapKind>,
+    span: Span,
+}
+
+fn parse_iter_usage(
+    cx: &LateContext<'tcx>,
+    ctxt: SyntaxContext,
+    mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
+) -> Option<IterUsage> {
+    let (kind, span) = match iter.next() {
+        Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
+            let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
+                (name, args)
+            } else {
+                return None;
+            };
+            let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
+            let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
+
+            match (&*name.ident.as_str(), args) {
+                ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Next, e.span),
+                ("next_tuple", []) => {
+                    if_chain! {
+                        if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
+                        if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
+                        if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did);
+                        if let ty::Tuple(subs) = subs.type_at(0).kind();
+                        if subs.len() == 2;
+                        then {
+                            return Some(IterUsage { kind: IterUsageKind::NextTuple, span: e.span, unwrap_kind: None });
+                        } else {
+                            return None;
+                        }
+                    }
+                },
+                ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
+                    if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
+                        let span = if name.ident.as_str() == "nth" {
+                            e.span
+                        } else {
+                            if_chain! {
+                                if let Some((_, Node::Expr(next_expr))) = iter.next();
+                                if let ExprKind::MethodCall(next_name, _, [_], _) = next_expr.kind;
+                                if next_name.ident.name == sym::next;
+                                if next_expr.span.ctxt() == ctxt;
+                                if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
+                                if cx.tcx.trait_of_item(next_id) == Some(iter_id);
+                                then {
+                                    next_expr.span
+                                } else {
+                                    return None;
+                                }
+                            }
+                        };
+                        match idx {
+                            0 => (IterUsageKind::Next, span),
+                            1 => (IterUsageKind::Second, span),
+                            _ => return None,
+                        }
+                    } else {
+                        return None;
+                    }
+                },
+                _ => return None,
+            }
+        },
+        _ => return None,
+    };
+
+    let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
+        match e.kind {
+            ExprKind::Call(
+                Expr {
+                    kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)),
+                    ..
+                },
+                _,
+            ) => {
+                let parent_span = e.span.parent().unwrap();
+                if parent_span.ctxt() == ctxt {
+                    (Some(UnwrapKind::QuestionMark), parent_span)
+                } else {
+                    (None, span)
+                }
+            },
+            _ if e.span.ctxt() != ctxt => (None, span),
+            ExprKind::MethodCall(name, _, [_], _)
+                if name.ident.name == sym::unwrap
+                    && cx
+                        .typeck_results()
+                        .type_dependent_def_id(e.hir_id)
+                        .map_or(false, |id| is_diag_item_method(cx, id, sym::option_type)) =>
+            {
+                (Some(UnwrapKind::Unwrap), e.span)
+            },
+            _ => (None, span),
+        }
+    } else {
+        (None, span)
+    };
+
+    Some(IterUsage {
+        kind,
+        unwrap_kind,
+        span,
+    })
+}
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 91606ed3b2b..b6438bce874 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -33,6 +33,7 @@ mod iter_nth_zero;
 mod iter_skip_next;
 mod iterator_step_by_zero;
 mod manual_saturating_arithmetic;
+mod manual_split_once;
 mod manual_str_repeat;
 mod map_collect_result_unit;
 mod map_flatten;
@@ -64,6 +65,7 @@ mod wrong_self_convention;
 mod zst_offset;
 
 use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::consts::{constant, Constant};
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
 use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
 use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
@@ -1771,6 +1773,29 @@ declare_clippy_lint! {
     "manual implementation of `str::repeat`"
 }
 
+declare_clippy_lint! {
+    /// **What it does:** Checks for usages of `str::splitn(2, _)`
+    ///
+    /// **Why is this bad?** `split_once` is both clearer in intent and slightly more efficient.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust,ignore
+    /// // Bad
+    ///  let (key, value) = _.splitn(2, '=').next_tuple()?;
+    ///  let value = _.splitn(2, '=').nth(1)?;
+    ///
+    /// // Good
+    /// let (key, value) = _.split_once('=')?;
+    /// let value = _.split_once('=')?.1;
+    /// ```
+    pub MANUAL_SPLIT_ONCE,
+    complexity,
+    "replace `.splitn(2, pat)` with `.split_once(pat)`"
+}
+
 pub struct Methods {
     avoid_breaking_exported_api: bool,
     msrv: Option<RustcVersion>,
@@ -1848,7 +1873,8 @@ impl_lint_pass!(Methods => [
     IMPLICIT_CLONE,
     SUSPICIOUS_SPLITN,
     MANUAL_STR_REPEAT,
-    EXTEND_WITH_DRAIN
+    EXTEND_WITH_DRAIN,
+    MANUAL_SPLIT_ONCE
 ]);
 
 /// Extracts a method call name, args, and `Span` of the method name.
@@ -2176,8 +2202,18 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
                     unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
                 }
             },
-            ("splitn" | "splitn_mut" | "rsplitn" | "rsplitn_mut", [count_arg, _]) => {
-                suspicious_splitn::check(cx, name, expr, recv, count_arg);
+            ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
+                if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+                    suspicious_splitn::check(cx, name, expr, recv, count);
+                    if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
+                        manual_split_once::check(cx, name, expr, recv, pat_arg);
+                    }
+                }
+            },
+            ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
+                if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+                    suspicious_splitn::check(cx, name, expr, recv, count);
+                }
             },
             ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
             ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints/src/methods/suspicious_splitn.rs
index a271df60572..1c546a15bf6 100644
--- a/clippy_lints/src/methods/suspicious_splitn.rs
+++ b/clippy_lints/src/methods/suspicious_splitn.rs
@@ -1,4 +1,3 @@
-use clippy_utils::consts::{constant, Constant};
 use clippy_utils::diagnostics::span_lint_and_note;
 use if_chain::if_chain;
 use rustc_ast::LitKind;
@@ -8,15 +7,8 @@ use rustc_span::source_map::Spanned;
 
 use super::SUSPICIOUS_SPLITN;
 
-pub(super) fn check(
-    cx: &LateContext<'_>,
-    method_name: &str,
-    expr: &Expr<'_>,
-    self_arg: &Expr<'_>,
-    count_arg: &Expr<'_>,
-) {
+pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
     if_chain! {
-        if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg);
         if count <= 1;
         if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
         if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
@@ -24,9 +16,9 @@ pub(super) fn check(
         if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id);
         then {
             // Ignore empty slice and string literals when used with a literal count.
-            if (matches!(self_arg.kind, ExprKind::Array([]))
+            if matches!(self_arg.kind, ExprKind::Array([]))
                 || matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
-            ) && matches!(count_arg.kind, ExprKind::Lit(_))
+
             {
                 return;
             }
diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs
index 658ac81f6ea..e7e249c79a2 100644
--- a/clippy_lints/src/unused_self.rs
+++ b/clippy_lints/src/unused_self.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_help;
-use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::visitors::is_local_used;
 use if_chain::if_chain;
 use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
 use rustc_lint::{LateContext, LateLintPass};
@@ -50,8 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
             if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
             let body = cx.tcx.hir().body(*body_id);
             if let [self_param, ..] = body.params;
-            let self_hir_id = self_param.pat.hir_id;
-            if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body);
+            if !is_local_used(cx, body, self_param.pat.hir_id);
             then {
                 span_lint_and_help(
                     cx,
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index ad341fbbda9..97062bcb282 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -132,7 +132,7 @@ define_Conf! {
     ///
     /// Suppress lints whenever the suggested change would cause breakage for other crates.
     (avoid_breaking_exported_api: bool = true),
-    /// Lint: MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE.
+    /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE.
     ///
     /// The minimum rust version that the project supports
     (msrv: Option<String> = None),
diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs
index 71cfa196fc3..9302e5c21fa 100644
--- a/clippy_utils/src/diagnostics.rs
+++ b/clippy_utils/src/diagnostics.rs
@@ -21,7 +21,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
                 "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
                 &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
                     // extract just major + minor version and ignore patch versions
-                    format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
+                    format!("rust-{}", n.rsplit_once('.').unwrap().1)
                 }),
                 lint
             ));
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index bd229402f41..603e831459d 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -2,6 +2,7 @@
 #![feature(in_band_lifetimes)]
 #![feature(iter_zip)]
 #![feature(rustc_private)]
+#![feature(control_flow_enum)]
 #![recursion_limit = "512"]
 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
 #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index 4a9c4fd0276..14234d9c9cb 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -13,6 +13,7 @@ macro_rules! msrv_aliases {
 // names may refer to stabilized feature flags or library items
 msrv_aliases! {
     1,53,0 { OR_PATTERNS }
+    1,52,0 { STR_SPLIT_ONCE }
     1,50,0 { BOOL_THEN }
     1,46,0 { CONST_IF_MATCH }
     1,45,0 { STR_STRIP_PREFIX }
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index b0c3fe1e5a7..d7e46c2d3eb 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -68,6 +68,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
 pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
 pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
 pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
+pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
 #[cfg(feature = "internal-lints")]
 pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
 #[cfg(feature = "internal-lints")]
diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs
index ce00106dd4d..503effbdad5 100644
--- a/clippy_utils/src/visitors.rs
+++ b/clippy_utils/src/visitors.rs
@@ -4,6 +4,7 @@ use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visito
 use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
 use rustc_lint::LateContext;
 use rustc_middle::hir::map::Map;
+use std::ops::ControlFlow;
 
 /// returns `true` if expr contains match expr desugared from try
 fn contains_try(expr: &hir::Expr<'_>) -> bool {
@@ -133,62 +134,6 @@ where
     }
 }
 
-pub struct LocalUsedVisitor<'hir> {
-    hir: Map<'hir>,
-    pub local_hir_id: HirId,
-    pub used: bool,
-}
-
-impl<'hir> LocalUsedVisitor<'hir> {
-    pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
-        Self {
-            hir: cx.tcx.hir(),
-            local_hir_id,
-            used: false,
-        }
-    }
-
-    fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
-        visit(self, t);
-        std::mem::replace(&mut self.used, false)
-    }
-
-    pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
-        self.check(arm, Self::visit_arm)
-    }
-
-    pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
-        self.check(body, Self::visit_body)
-    }
-
-    pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
-        self.check(expr, Self::visit_expr)
-    }
-
-    pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
-        self.check(stmt, Self::visit_stmt)
-    }
-}
-
-impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
-    type Map = Map<'v>;
-
-    fn visit_expr(&mut self, expr: &'v Expr<'v>) {
-        if self.used {
-            return;
-        }
-        if path_to_local_id(expr, self.local_hir_id) {
-            self.used = true;
-        } else {
-            walk_expr(self, expr);
-        }
-    }
-
-    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
-        NestedVisitorMap::OnlyBodies(self.hir)
-    }
-}
-
 /// A type which can be visited.
 pub trait Visitable<'tcx> {
     /// Calls the corresponding `visit_*` function on the visitor.
@@ -203,7 +148,22 @@ macro_rules! visitable_ref {
         }
     };
 }
+visitable_ref!(Arm, visit_arm);
 visitable_ref!(Block, visit_block);
+visitable_ref!(Body, visit_body);
+visitable_ref!(Expr, visit_expr);
+visitable_ref!(Stmt, visit_stmt);
+
+// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
+// where
+//     I::Item: Visitable<'tcx>,
+// {
+//     fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+//         for x in self {
+//             x.visit(visitor);
+//         }
+//     }
+// }
 
 /// Calls the given function for each break expression.
 pub fn visit_break_exprs<'tcx>(
@@ -260,3 +220,48 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
     v.visit_expr(&cx.tcx.hir().body(body).value);
     v.found
 }
+
+/// Calls the given function for each usage of the given local.
+pub fn for_each_local_usage<'tcx, B>(
+    cx: &LateContext<'tcx>,
+    visitable: impl Visitable<'tcx>,
+    id: HirId,
+    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+    struct V<'tcx, B, F> {
+        map: Map<'tcx>,
+        id: HirId,
+        f: F,
+        res: ControlFlow<B>,
+    }
+    impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
+        type Map = Map<'tcx>;
+        fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+            NestedVisitorMap::OnlyBodies(self.map)
+        }
+
+        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+            if self.res.is_continue() {
+                if path_to_local_id(e, self.id) {
+                    self.res = (self.f)(e);
+                } else {
+                    walk_expr(self, e);
+                }
+            }
+        }
+    }
+
+    let mut v = V {
+        map: cx.tcx.hir(),
+        id,
+        f,
+        res: ControlFlow::CONTINUE,
+    };
+    visitable.visit(&mut v);
+    v.res
+}
+
+/// Checks if the given local is used.
+pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
+    for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
+}
diff --git a/tests/compile-test.rs b/tests/compile-test.rs
index 0a82377a10e..6116acffe07 100644
--- a/tests/compile-test.rs
+++ b/tests/compile-test.rs
@@ -39,6 +39,7 @@ fn third_party_crates() -> String {
         "clippy_lints",
         "clippy_utils",
         "if_chain",
+        "itertools",
         "quote",
         "regex",
         "serde",
diff --git a/tests/ui/manual_split_once.fixed b/tests/ui/manual_split_once.fixed
new file mode 100644
index 00000000000..3a0332939d4
--- /dev/null
+++ b/tests/ui/manual_split_once.fixed
@@ -0,0 +1,50 @@
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::manual_split_once)]
+#![allow(clippy::iter_skip_next, clippy::iter_nth_zero)]
+
+extern crate itertools;
+
+#[allow(unused_imports)]
+use itertools::Itertools;
+
+fn main() {
+    let _ = Some("key=value".split_once('=').map_or("key=value", |x| x.0));
+    let _ = "key=value".splitn(2, '=').nth(2);
+    let _ = "key=value".split_once('=').map_or("key=value", |x| x.0);
+    let _ = "key=value".split_once('=').map_or("key=value", |x| x.0);
+    let _ = "key=value".split_once('=').unwrap().1;
+    let _ = "key=value".split_once('=').unwrap().1;
+    let (_, _) = "key=value".split_once('=').unwrap();
+
+    let s = String::from("key=value");
+    let _ = s.split_once('=').map_or(&*s, |x| x.0);
+
+    let s = Box::<str>::from("key=value");
+    let _ = s.split_once('=').map_or(&*s, |x| x.0);
+
+    let s = &"key=value";
+    let _ = s.split_once('=').map_or(*s, |x| x.0);
+
+    fn _f(s: &str) -> Option<&str> {
+        let _ = s.split_once("key=value").map_or(s, |x| x.0);
+        let _ = s.split_once("key=value")?.1;
+        let _ = s.split_once("key=value")?.1;
+        None
+    }
+
+    // Don't lint, slices don't have `split_once`
+    let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap();
+}
+
+fn _msrv_1_51() {
+    #![clippy::msrv = "1.51"]
+    // `str::split_once` was stabilized in 1.16. Do not lint this
+    let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+}
+
+fn _msrv_1_52() {
+    #![clippy::msrv = "1.52"]
+    let _ = "key=value".split_once('=').unwrap().1;
+}
diff --git a/tests/ui/manual_split_once.rs b/tests/ui/manual_split_once.rs
new file mode 100644
index 00000000000..e6093b63fe8
--- /dev/null
+++ b/tests/ui/manual_split_once.rs
@@ -0,0 +1,50 @@
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::manual_split_once)]
+#![allow(clippy::iter_skip_next, clippy::iter_nth_zero)]
+
+extern crate itertools;
+
+#[allow(unused_imports)]
+use itertools::Itertools;
+
+fn main() {
+    let _ = "key=value".splitn(2, '=').next();
+    let _ = "key=value".splitn(2, '=').nth(2);
+    let _ = "key=value".splitn(2, '=').next().unwrap();
+    let _ = "key=value".splitn(2, '=').nth(0).unwrap();
+    let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+    let _ = "key=value".splitn(2, '=').skip(1).next().unwrap();
+    let (_, _) = "key=value".splitn(2, '=').next_tuple().unwrap();
+
+    let s = String::from("key=value");
+    let _ = s.splitn(2, '=').next().unwrap();
+
+    let s = Box::<str>::from("key=value");
+    let _ = s.splitn(2, '=').nth(0).unwrap();
+
+    let s = &"key=value";
+    let _ = s.splitn(2, '=').skip(0).next().unwrap();
+
+    fn _f(s: &str) -> Option<&str> {
+        let _ = s.splitn(2, "key=value").next()?;
+        let _ = s.splitn(2, "key=value").nth(1)?;
+        let _ = s.splitn(2, "key=value").skip(1).next()?;
+        None
+    }
+
+    // Don't lint, slices don't have `split_once`
+    let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap();
+}
+
+fn _msrv_1_51() {
+    #![clippy::msrv = "1.51"]
+    // `str::split_once` was stabilized in 1.16. Do not lint this
+    let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+}
+
+fn _msrv_1_52() {
+    #![clippy::msrv = "1.52"]
+    let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+}
diff --git a/tests/ui/manual_split_once.stderr b/tests/ui/manual_split_once.stderr
new file mode 100644
index 00000000000..4f15196b469
--- /dev/null
+++ b/tests/ui/manual_split_once.stderr
@@ -0,0 +1,82 @@
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:13:13
+   |
+LL |     let _ = "key=value".splitn(2, '=').next();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `Some("key=value".split_once('=').map_or("key=value", |x| x.0))`
+   |
+   = note: `-D clippy::manual-split-once` implied by `-D warnings`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:15:13
+   |
+LL |     let _ = "key=value".splitn(2, '=').next().unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').map_or("key=value", |x| x.0)`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:16:13
+   |
+LL |     let _ = "key=value".splitn(2, '=').nth(0).unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').map_or("key=value", |x| x.0)`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:17:13
+   |
+LL |     let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:18:13
+   |
+LL |     let _ = "key=value".splitn(2, '=').skip(1).next().unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:19:18
+   |
+LL |     let (_, _) = "key=value".splitn(2, '=').next_tuple().unwrap();
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=')`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:22:13
+   |
+LL |     let _ = s.splitn(2, '=').next().unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(&*s, |x| x.0)`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:25:13
+   |
+LL |     let _ = s.splitn(2, '=').nth(0).unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(&*s, |x| x.0)`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:28:13
+   |
+LL |     let _ = s.splitn(2, '=').skip(0).next().unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(*s, |x| x.0)`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:31:17
+   |
+LL |         let _ = s.splitn(2, "key=value").next()?;
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value").map_or(s, |x| x.0)`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:32:17
+   |
+LL |         let _ = s.splitn(2, "key=value").nth(1)?;
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:33:17
+   |
+LL |         let _ = s.splitn(2, "key=value").skip(1).next()?;
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1`
+
+error: manual implementation of `split_once`
+  --> $DIR/manual_split_once.rs:49:13
+   |
+LL |     let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
+
+error: aborting due to 13 previous errors
+