Different behavior of closure return type inference (lifetime-related) when the "body" is a BlockExpression

trait MyTrait {}

struct NullableMyTraitObject<'a>(Option<Box<dyn MyTrait + 'a>>);

impl<'a> NullableMyTraitObject<'a> {
    // This doesn't compile.
    pub fn get_mut(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| boxed.as_mut())
    }

    // But this does.
    pub fn get_mut2(&mut self) -> Option<&'_ mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| boxed.as_mut() as _)
    }

    // This compiles too.
    pub fn get_mut3(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| { boxed.as_mut() })
    }

    // But this doesn't.
    pub fn get_mut4(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| (boxed.as_mut()))
    }

    // Neither does this.
    pub fn get_mut5(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| if true {
            boxed.as_mut()
        } else {
            boxed.as_mut()
        })
    }

    // Or this.
    pub fn get_mut6(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| match true {
            true => boxed.as_mut(),
            false => boxed.as_mut()
        })
    }

    // The workaround applies here.
    pub fn get_mut7(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| if true {
            boxed.as_mut() as _
        } else {
            boxed.as_mut() as _
        })
    }

    // And here, too.
    pub fn get_mut8(&mut self) -> Option<&mut dyn MyTrait> {
        self.0.as_mut().map(|boxed| match true {
            true => boxed.as_mut() as _,
            false => boxed.as_mut() as _
        })
    }
}

The compiler complained:

error: lifetime may not live long enough
 --> src/lib.rs:8:37
  |
5 | impl<'a> NullableMyTraitObject<'a> {
  |      -- lifetime `'a` defined here
6 |     // This doesn't compile.
7 |     pub fn get_mut(&mut self) -> Option<&mut dyn MyTrait> {
  |                    - let's call the lifetime of this reference `'1`
8 |         self.0.as_mut().map(|boxed| boxed.as_mut())
  |                                     ^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
  |
help: consider adding 'move' keyword before the nested closure
  |
8 |         self.0.as_mut().map(move |boxed| boxed.as_mut())
  |                             ++++

It seems that although we want Option<&'_ mut dyn MyTrait> (the implicit lifetime comes from &mut self) as the return type of the closure, the actual inferred type is Option<&'a mut dyn MyTrait> based on the type of boxed.as_mut().

Is it true that the "body" of a closure is not a coercion site unless it is wrapped in {} as a block? Is this some undocumented detail, since according to Coercion sites in Type coercions - The Rust Reference:

If the expression in one of these coercion sites is a coercion-propagating expression, then the relevant sub-expressions in that expression are also coercion sites. Propagation recurses from these new coercion sites. Propagating expressions and their relevant sub-expressions are:

  • ...
  • Parenthesized sub-expressions ((e)): if the expression has type U, then the sub-expression is a coercion site to U.
  • Blocks: if a block has type U, then the last expression in the block (if it is not semicolon-terminated) is a coercion site to U. This includes blocks which are part of control flow statements, such as if/else, if the block has a known type.

but surrounding the closure "body" with () doesn't work as with {} does.

I think the difference in behavior is quite confusing. Also, the suggestion to add move keyword is misleading to beginners in this case, and it's quite obvious because the variable involved in the lifetime issue isn't a captured variable.

It does seem like some coercion bug. But these work, so it's not that simple.

    pub fn get_mut<'s>(&'s mut self) -> Option<&'s mut dyn MyTrait> {
        let l: fn(&'s mut Box<dyn MyTrait + 'a>) -> &'s mut dyn MyTrait =
            |boxed| boxed.as_mut();
        self.0.as_mut().map(l)
    }
fn id<'s, 'a: 's, F>(f: F) -> F 
where
    F: FnOnce(&'s mut Box<dyn MyTrait + 'a>) -> &'s mut dyn MyTrait
{ f }
// ...
    pub fn get_mut<'s>(&'s mut self) -> Option<&'s mut dyn MyTrait> {
        self.0.as_mut().map(id(|boxed| boxed.as_mut()))
    }

There's an existing issue for this. It also links to a former topic.

I think the coercion happening is from &'self mut dyn MyTrait + 'a to &'self mut dyn MyTrait + 'self. If you return the first one, it's more correct, aka less restrictive for the caller. It also fixes the errors.

Still no idea why the {} matter though.