The second example fails because Box::pin does not also erase the type of the future like .boxed() does. However, type annotations to force the erasure make it succeed:
let pinboxed: Pin<Box<dyn Send + Future<Output = ()>>> = Box::pin(future);
The second one works because type inference is clever enough to see through it all and conclude the correct types.
Another way to make the second example work is to insert an explicit coercion point, so that Rust knows that it should do something with the value (and this "something" happens to be unsizing); the corresponding line will look like
let pinboxed = Box::pin(future) as _;
(this _ is then filled by the compiler to be Pin<Box<dyn Send + Future<Output = ()>>>).
Almost got it. I tried something like... let pinboxed = Box::pin(future) as Pin<Box<dyn Future<Output = ()> + Send>>;
...but didn't work. I must be did something wrong.