The problem here seems to be with type inference. Desugaring the ?
gives something like
let foo: &FooRef = &match Try::branch(make_foo()) {
ControlFlow::Continue(v) => v,
ControlFlow::Break(r) => return FromResidual::from_residual(r),
};
at some point between determining the return type of make_foo()
, looking up the Try
implementation, type-checking the ControlFlow::Continue(v) => v
arm, and connecting this type information to the return value of the match
block, there must be a point where the information that the whole thing evaluates to a Foo
doesn’t propagate “fast enough” or “obviously enough” in order to the compiler to consider this place as a candidate for coercion anymore.
I don’t know if that’s something that can be easily improved. Intuitively speaking, instead of trying to coerce the &Foo
to &FooRef
, the compiler could also, alternatively, consider the possibility of coercing, say, the Result<Foo, …>
to Result<FooRef, …>
in the call to Try::branch
, or perhaps the Foo
to FooRef
in the match arm, so there’s perhaps also some ambiguity.
In any case, you can help out the compiler here and do some explicit coercion by introducing an as _
; then it’s all clear where exactly you want to convert, and the code compiles:
let foo: &FooRef = &make_foo()? as _;
As to the question why a !Sized
target type BarRef
leads to different behavior, I have some theories: It might be that there’s less ambiguity because you cannot have BarRef
in Result
or by-value anymore, so the options mentioned above for other places that could do some coersion disappear.
Or perhaps, the way to think about this is that while type inference (for the Sized
case) can, after determining to perhaps not consider the outer =
assignment for a place of coercion, proceed to determine that
- the
&match … { }
is &FooRef
, hence
- the
match … { }
is FooRef
, hence
- the
v
return value in the match arm is FooRef
(and so on)
whilst with BarRef: !Sized
, match … { }
can no longer be BarRef
because you can’t have !Sized
types by-value, and for some reason, due to this, if can go back to considering the assignment a coercion site again, after all.
Or perhaps, type inference is more nonlinear, starting in the middle: it starts out with match … { }
being some abstract yet sized type T
, then determining that &match … { }
is &T
, meaning that when matching &T
against &BarRef
, you do get a mismatch (since BarRef
is !Sized
) inducing a coercion-site, whereas for FooRef
it simply unifies T == FooRef
and proceeds.
TL;DR, I don’t really know how exactly type inference works in Rust, and it’s often a bit confusing and inconsistent, but also type inference while (potentially) allowing coercions in so many places is probably a hard problem to begin with.