Code change:
If I return a direct type that is , I get a helpful compiler warning: The equivalent (and I believe preferred) code using gives no such useful feedback: Amusingly, this isn't a new problem, as returning a boxed trait object also has the same problem. It's arguably worse there because you've performed an allocation for no good reason, but it's also less likely because people are more reluctant to introduce unneeded allocations.
Since Rust 1.27 stabilizes (on functions), one possible solution would be a lint that checks the compiler-known type underneath the and the function returning it. If the type has the annotation but the function does not, it could suggest adding the attribute to the function.
I think it would make sense for to quickly review and agree on how we should handle this. A lint seems plausible. Inside the same module, where we know the concrete underlying type, it would definitely make sense to pass through the . I'd love to know if it makes sense to automatically pass through the on the concrete type when used from outside the module, so that this can work automatically without needing a lint and a manual additional on the function.
Why is the not on, say, ?
I agree with that traits seem like the better solution here than trying to "leak" must-used-ness.
In that case, is it fine that returning an of real type will drop the must-used-ness? Maybe we should suggest to the author of the function to add the attribute to the function if the real type is
Right, there's nothing meaningfully tying such a function to , if is hidden.
The new thing being stabilized in 1.27 is specifically on functions and methods (). on types, including the iterator-adapter structs like , are .
We may need to add an arm for in this match?
So, I'd absolutely support adding for traits, which would have the same effect on a function returning as on a type T would have for a function returning . However, I also think we ought to handle this case somehow, preferably in a way that doesn't require the user to manually propagate outward on every function that returns a type.
I don't think we should. The type, including its must-use'dness, is not a part of the public API in that context. If you want the function return to be treated as must-use, the solution in that case is to tag the function as must-use.
Then in that case, would it make sense to have a lint suggesting propagation of from the type to the function that returns that concrete type?
I don't think so. The point of returning impl Trait is to hide the return type and the facts of its API, the fact that the type is must-use seems no different from other facts about the return type.
As alluded to above, I argue that this is a bug in the must-use lint due to its antedating impl-Trait; I don't see sufficient cause here to complicate the language with another lint or feature. A pull request is forthcoming.
Hm, perhaps this was too confidently worded: see description of the new pull request .
So let me summarize to see if I understand correctly what you're planning for must_use for traits:
I think expanding the applicability of mustuse should probably be introduced using an RFC. It seems to me that there are two different approaches proposed and an RFC could clarify why we chose a certain approach: proposes to evaluate the lint using the concrete type and provided a PR to this effect. This is incompatible with statement with which I agree with: "The type, including its must-use'dness, is not a part of the public API in that context [i.e. return impl Trait]". I also agree with that mustuse for traits is something we should investigate and pursue. This would be extremely helpful for futures because forgetting to poll needs to be obvious.
Can you provide an example of a case where it is the correct thing to return a concrete type that is tagged as through and not use the result of the function? My original premise is that this is an unintentional mistake in 95-99% of the cases, which feels like the ideal case for a lint. In the (rare) case where the author of the -returning function deliberately wants to allow forgetting to use the return value, they can allow the lint. I don't know exactly which "other facts" you mean, but I can think of two primary things: "sub" traits, which are not automagically propagated: traits, which are automagically propagated: I think that the case is different from the "sub trait" case because you will rather quickly run into "well, my code doesn't compile" errors when you forget to return the "sub trait". You then have to decide if you meant to expose that interface. I know the auto trait aspect was much discussed, so I don't wish to re-litigate it. My point here is that we : I think there's a case to be made that is already an ergonomics-based feature as it helps prevent the user from writing code they didn't mean to.
As a concrete side note, I opened this because I did it myself: I wrote some code that returned and was confused for a minute because the compiler didn't have any warnings. I'm used to those warnings and they are part of my development flow. Having them "suddenly go missing" just because I wanted to use felt like trying to step on the last stair that isn't there. For me, it's right up there with in that my "normal" flow of programming that I've established since ~Rust 0.12 doesn't quite work the same with .
IMO the more conservative approach would be to not leak mustuse on the concrete type. If that turns out to be the wrong call, it can always be later. I think defining mustuse on traits (e.g. on and ) will already be enough to catch all the bugs we're trying to prevent. The big advantage of defining mustuse on the trait is that this system can also work for unnameable types like the return type of an async function. Additionally it also avoids all the boilerplate code that defines each concrete iterator or future type as muchuse.
I think the current approach of "stick on the dozens of types implementing and " just doesn't scale and this is an example where it goes wrong. Also consider type parameters or associated types that are known implement those traits. IMO they should get the same treatment by , without knowing the concrete type.
To be clear, I'm totally happy with implementing for traits, but that particular option has been available "forever" but doesn't seem to be implemented for whichever reason. On the flip side, fairly quickly. I really just don't want to see this particular feature held up by a months (years?) long discussion of the ideal Platonic implementation of for traits. Isn't that what the current implementation does? Am I missing something? I believe that's what I'm proposing... Yes, I believe that that's a better option, and I'll add one more: presumably it will also work for trait objects. I just don't want to lose what existing ergonomics we have until someone implements for traits. These don't even have to be mutually exclusive options! The lint can catch cases that a trait wouldn't — such as when a given instance of a trait should be must use but not the trait itself. The lint could even be removed once the better solution is created, if we felt it didn't carry its weight anymore.
I also want to suggest a more aggressive approach: existential is always no matter of its underlying type. If a function is returning an thing, you were probably meant to use it in some way.
I wonder how hard it would be to turn such a thing on to see the impact.
Thanks for the compelling argument! I agree with what I think is the implication of your post: this question boils down to "Is more like an auto trait, or a regular API fact?" I'm not sure of the answer. What is the distinction with vs any other return type? Isn't it plausible that something that returns also has a useful side effect, and the return is not relevant all the time?

Related discussion:
text: } let t = cx.tables.expr_ty(&expr); <del> // FIXME(varkor): replace with `t.is_unit() || t.conservative_is_uninhabited()`. let type_permits_no_use = match t.sty { ty::Tuple(ref tys) if tys.is_empty() => true, ty::Never => true, ty::Adt(def, _) => { if def.variants.is_empty() { true } else { check_must_use(cx, def.did, s.span, "") </del> <ins> let type_permits_lack_of_use = if t.is_unit() || cx.tcx.is_ty_uninhabited_from(cx.tcx.hir.get_module_parent(expr.id), t) { true } else { match t.sty { ty::Adt(def, _) => check_must_use(cx, def.did, s.span, "", ""), ty::Opaque(def, _) => { let mut must_use = false; for (predicate, _) in &cx.tcx.predicates_of(def).predicates { if let ty::Predicate::Trait(ref poly_trait_predicate) = predicate { let trait_ref = poly_trait_predicate.skip_binder().trait_ref; if check_must_use(cx, trait_ref.def_id, s.span, "implementer of ", "") { must_use = true; break; } } } must_use } ty::Dynamic(binder, _) => { let mut must_use = false; for predicate in binder.skip_binder().iter() { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate { if check_must_use(cx, trait_ref.def_id, s.span, "", " trait object") { must_use = true; break; } } } must_use </ins> } <ins> _ => false, </ins> } <del> _ => false, </del> }; let mut fn_warned = false;