Weird inference error when using "?"

I wrote try_new_cyclic_rc function and it seams to work correctly:

pub fn try_new_cyclic_rc<T: Sized, F>(f: F) -> Result<Rc<T>, String>
    where
        F: FnOnce(&Weak<T>) -> Result<T, String>,
{
    // implementation doesn't matter
}

But I receive a compilation error when calling "?" in the closure...

trait BaseObject {}

struct ViewObject {
    parent: Weak<dyn BaseObject>,
    child: Option<Rc<dyn BaseObject>>,
}

impl BaseObject for ViewObject {}

fn create_child_object(parent: Weak<dyn BaseObject>) -> Result<Rc<ViewObject>, String> {
    Ok(Rc::new(ViewObject { parent, child: None }))
}

fn create_container_object(parent: Weak<dyn BaseObject>) -> Result<Rc<ViewObject>, String> {
    try_new_cyclic_rc(|weak| {
        let child = create_child_object(weak.clone())?;
        Ok(ViewObject { parent, child: Some(child) })
    })
}

Here is the error:

error[E0277]: the size for values of type `dyn BaseObject` cannot be known at compilation time
  --> src/main.rs:38:5
   |
38 |     try_new_cyclic_rc(|weak| {
   |     ^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn BaseObject`
note: required by a bound in `try_new_cyclic_rc`
  --> src/main.rs:4:26
   |
4  | pub fn try_new_cyclic_rc<T: Sized>(f: impl FnOnce(&Weak<T>) -> Result<T, String>) -> Result<Rc<T>, String> {
   |                          ^ required by this bound in `try_new_cyclic_rc`


error[E0277]: the size for values of type `dyn BaseObject` cannot be known at compilation time
  --> src/main.rs:42:54
   |
42 |         let child = create_child_object(weak.clone())?;
   |                                                      ^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn BaseObject`
   = help: the following other types implement trait `FromResidual<R>`:
             <Result<T, F> as FromResidual<Result<Infallible, E>>>
             <Result<T, F> as FromResidual<Yeet<E>>>
   = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, String>>` for `Result<dyn BaseObject, String>`

So the question is why? Why does it try to return dyn BaseObject when it should be bound by both create_container_object function return type and Ok(ViewObject {...}) result?

I found a "solution" by explicitly specifying T try_new_cyclic_rc<Rc<ViewObject>, _>(...) but because of this usability is awful... :frowning:

The thing that seems to be misguiding the compiler here is

create_child_object(weak.clone())

Where it sees create_child_object takes a Weak<dyn BaseObject> and concludes that's what weak is, versus something that could coerce to Weak<dyn BaseObject>. It's not the ? specifically; you can replace it with .unwrap() and get the same errors.

With this change, the compiler gets the message that weak might not be Weak<dyn BaseObject> exactly, and apparently that's enough for it to take the result type of the function into consideration, causing everything to resolve correctly.

     try_new_cyclic_rc(|weak| {
-        let child = create_child_object(weak.clone())?;
+        let child = create_child_object(weak.clone() as _)?;
         Ok(ViewObject { parent, child: Some(child) })
     })

Another alternative is annotating the closure header:

    try_new_cyclic_rc(|weak: &Weak<ViewObject>| {
    // or
    try_new_cyclic_rc(|weak| -> Result<ViewObject, String> {

And yet another approach is to move the unsized coercion into create_child_object, but this does change its API and thus might not work for you.


This general case (where the return value would guide inference correctly, but the type resolver wants to find something before considering that context) reminds me of an existing issue, but I've failed to track it down so far; if I find it I'll add a link.

1 Like

? has a ton of flexibility. That's not a problem when you're in function scope, since the return type must always be fully annotated, but when you're in a closure like this -- where the return type also gets inferred -- it's much easier for the compiler to have too many options to be willing to pick one.

You can see some more examples of this in the draft RFC I'm writing to propose a simple way to improve things: https://github.com/scottmcm/rfcs/blob/try-again/text/0000-resolving-try-annotations.md#motivation

1 Like

If you're on nightly, a less-breaking change [1] is:

#![feature(unsize)]
use core::marker::Unsize;

fn create_child_object<P>(parent: Weak<P>) -> Result<Rc<ViewObject>, String>
where
    P: ?Sized + Unsize<dyn BaseObject>
{
    Ok(Rc::new(ViewObject { parent, child: None }))
}

Or on stable, you can actually emulate this yourself.

It's still a breaking change, but mostly backwards compatible. Being more general, it may cause new inference failures elsewhere.


  1. as create_child_object can still take a Weak<dyn BaseObject> ↩ī¸Ž

So basically I have no way around but to add type annotation at least somewhere (closure arg, closure return, T in try_new_cyclic_rc).

In my case this doesn't really help either, as I have many create_xxx_object methods.

From what I've tested it seams that compiler just takes first available line with the type in question to test inference. Just as an example if I add one unreachable statement everything seams to work (but it must be added at the beginning):

    try_new_cyclic_rc(|weak| {
        if false {
            return Ok(ViewObject { parent, child: None });
        }
        let child = create_child_object(weak.clone())?;
        Ok(ViewObject { parent, child: Some(child) })
    })

Frankly I hoped that compiler will compute all possible scenarios and select the one to fit, or at the very least scan whole closure instead of just first mention. Sadly, this is not the case :frowning:

PS: I've actually reproduced the same issue with Rc:new_cyclic, so "?" is really not to blame but rather weak.clone().

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.