Change:
I've seen a lot of beginning Rust programmers make a mistake similar to the program below, which compiles and runs without warning: The bug in this program is hard for new Rust programmers to spot. The compiler should help them, by warning that does not return a value. This could be done with a new attribute , applied to functions like . This would be the inverse of : It would warn only if the result of the function is used. Like , it would take an optional argument that provides an additional diagnostic message. (Bikeshed: may be a confusing name. Other suggestions welcome.) Or perhaps any assignment where has type should cause a warning by default. This would be a more general solution, catching more possible errors but also potentially causing more disruption. The warnings in this case might be less helpful, because they wouldn't include suggestions tailored to specific functions.
It seems like we at least want the warning, since that's entirely useless AFAICT.
It could possibly occur legitimately due to a macro, though. For example, this silly macro doesn't care whether the expression returns or a real value:
Well, in that situation the macro writer could just do a local .
How about nested -- would you also warn on this? IOW, is this just about the RHS being , or about binding a name to at all?
I think this is about binding a name to , there's not really a good reason to do so IMO. Maybe the lint would then be less about the "this function returns unit" (though that could be a special case to provide better messaging on), but more so that any binding with a type of is linted on. I'm trying to think of what exactly makes special -- e.g. it seems like should also be linted against -- but not really coming up with anything concrete. Certainly ZST + Copy isn't right.
I think it would be reasonable to warn on any binding to . If the program never reads from the binding, then it already triggers an warning. If it does read from it, this is probably a bug and would benefit from a warning. Though any solution that captures simple cases like my original example is probably sufficient to provide most of the benefit here. may not be uniquely special, but because it is the default return type for Rust functions and blocks, and is supposed to be used for all expressions that exist only for their side effects, it's probably the only such type that matters in practice for 99% of programs. I see little practical benefit from trying to catch every possible case where a return value is useless. Types like show up mainly in generic or macro code where this lint wouldn't be useful, anyway.
I definitely agree that catching just unit is probably enough. I was mostly trying to come up with some cases where I also want this for non-unit types -- but I was unable to do so. I'm going to nominate this issue for lang to hopefully gauge whether we want an FCP here or if adding a lint like this (naming to be bikeshedded) needs a full RFC. I personally lean towards maybe an FCP, maybe even just "no one in the room has objections" approval (we'd have some time to roll it back if necessary, of course).
Clippy: https://rust-
Of course clippy has this covered. There's probably even an xkcd for that. It was only recently downgraded to pedantic (allowed):
I agree with that the bug there (with inference) seems like a hard blocker. But it seems plausible that we'd perhaps want it in rustc regardless as allow by default to start. Maybe if we throw compiler team at it someone will come up with an idea how to do it (seems related to work on the ! type inference).
is a special binding -- it should be exempt from this. That binding can also be avoided while still affecting type inference: . However, clippy continues to lint this. I haven't looked at the code, but it seems like clippy is only looking at the expression, not the pattern bindings.
To bikeshed this further, i would argue that should not trigger the lint (even with non- binding, and even when no inference is involved), on the premise that the author is already aware of the type, and this must be deliberate.
I think that this belongs in rustc (I have argued as much in the past) and the rules should be: Ignore let assignments with a user provided type Ignore macros Ignore _
Agreed. The lint should cover the case where the user doesn't explicitly write the type .
We discussed this further in today's language team meeting, and came to the following consensus: We should lint on if we infer as type . We should suppress that lint if you write the type explicitly: We should do a crater run to see how noisy this will be, before merging it.

Discussion:
lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op .label = this function will not propagate the caller location <ins> lint_unit_bindings = binding has unit type `()` .label = this pattern is inferred to be the unit type `()` </ins> lint_unknown_gated_lint = unknown lint: `{$name}` .note = the `{$name}` lint is unstable

mod reference_casting; mod traits; mod types; <ins> mod unit_bindings; </ins> mod unused; pub use array_into_iter::ARRAY_INTO_ITER;

use reference_casting::*; use traits::*; use types::*; <ins> use unit_bindings::*; </ins> use unused::*; /// Useful for other parts of the compiler / Clippy.

InvalidReferenceCasting: InvalidReferenceCasting, // Depends on referenced function signatures in expressions UnusedResults: UnusedResults, <ins> UnitBindings: UnitBindings, </ins> NonUpperCaseGlobals: NonUpperCaseGlobals, NonShorthandFieldPatterns: NonShorthandFieldPatterns, UnusedAllocation: UnusedAllocation,

fluent::lint_async_fn_in_trait } } <ins> #[derive(LintDiagnostic)] #[diag(lint_unit_bindings)] pub struct UnitBindingsDiag { #[label] pub label: Span, } </ins>

<ins> use crate::lints::UnitBindingsDiag; use crate::{LateLintPass, LintContext}; use rustc_hir as hir; use rustc_middle::ty::Ty; declare_lint! { /// The `unit_bindings` lint detects cases where bindings are useless because they have /// the unit type `()` as their inferred type. The lint is suppressed if the user explicitly /// annotates the let binding with the unit type `()`, or if the let binding uses an underscore /// wildcard pattern, i.e. `let _ = expr`, or if the binding is produced from macro expansions. /// /// ### Example /// /// ```rust,compile_fail /// #![deny(unit_bindings)] /// /// fn foo() { ///     println!("do work"); /// } /// /// pub fn main() { ///     let x = foo(); // useless binding /// } /// ``` /// /// {{produces}} /// /// ### Explanation /// /// Creating a local binding with the unit type `()` does not do much and can be a sign of a /// user error, such as in this example: /// /// ```rust,no_run /// fn main() { ///     let mut x = [1, 2, 3]; ///     x[0] = 5; ///     let y = x.sort(); // useless binding as `sort` returns `()` and not the sorted array. ///     println!("{:?}", y); // prints "()" /// } /// ``` pub UNIT_BINDINGS, Allow, "binding is useless because it has the unit `()` type" } declare_lint_pass!(UnitBindings => [UNIT_BINDINGS]); impl<'tcx> LateLintPass<'tcx> for UnitBindings { fn check_local(&mut self, cx: &crate::LateContext<'tcx>, local: &'tcx hir::Local<'tcx>) { // Suppress warning if user: // - explicitly ascribes a type to the pattern // - explicitly wrote `let pat = ();` // - explicitly wrote `let () = init;`. if !local.span.from_expansion() && let Some(tyck_results) = cx.maybe_typeck_results() && let Some(init) = local.init && let init_ty = tyck_results.expr_ty(init) && let local_ty = tyck_results.node_type(local.hir_id) && init_ty == Ty::new_unit(cx.tcx) && local_ty == Ty::new_unit(cx.tcx) && local.ty.is_none() && !matches!(init.kind, hir::ExprKind::Tup([])) && !matches!(local.pat.kind, hir::PatKind::Tuple([], ..)) { cx.emit_spanned_lint( UNIT_BINDINGS, local.span, UnitBindingsDiag { label: local.pat.span }, ); } } } </ins>

// run-pass #![allow(unused_parens)] <ins> #![allow(unit_bindings)] </ins> // pretty-expanded FIXME #23616 fn f<T, F>(g: F) -> T where F: FnOnce() -> T { g() }

<del> // Variant of diverging-falllback-control-flow that tests </del> <ins> // Variant of diverging-fallback-control-flow that tests </ins> // the specific case of a free function with an unconstrained // return type. This captures the pattern we saw in the wild // in the objc crate, where changing the fallback from `!` to `()`

// revisions: nofallback fallback #![cfg_attr(fallback, feature(never_type, never_type_fallback))] <ins> #![allow(unit_bindings)] </ins> fn make_unit() {}