3

While using boxed closures I ran into the following issue:

type Test = Rc<dyn Fn() -> i64>;

fn test_bad() -> Test {
    Test::new(|| 42)
}

fn test_good() -> Test {
    Rc::new(|| 42)
}

In the first case, I'm using the type alias to refer to the new method, whereas I'm using Rc directly in the second case.

In the first case, the compiler complains:

    |       Test::new(|| 42)
    |             ^^^ function or associated item not found in `Rc<(dyn Fn() -> i64 + 'static)>`
    |
    = note: the method `new` exists but the following trait bounds were not satisfied:
            `dyn Fn() -> i64: Sized`

But the second case works just fine. Can somebody please explain the difference? Is there any way to refer to new through the type alias or do I need to wrap it myself?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
max
  • 1,048
  • 10
  • 20
  • 1
    [Error when passing Rc as a function argument](https://stackoverflow.com/q/63893847/3650362) might also be found helpful. – trent Feb 14 '21 at 13:48

2 Answers2

4

In test_good, by calling Rc::new on || 42, you don't create an Rc<dyn Fn() -> i64>, but an Rc<ClosureType>, where ClosureType is the unique type of the closure provided, and it is sized. Then, since the Rc<ClosureType> is returned in a function that returns Rc<dyn Fn() -> i64>, it implicitly casts it to a trait object.

test_bad fails because instead of going from constructing an Rc of a sized closure then casting it to an Rc of an unsized trait object, it tries to directly construct an Rc of an unsized trait object, which fails since all arguments to functions must be sized.

I don't think there's a direct way to refer to new through the type alias, although you could very easily make your own:

fn new_test(func: impl 'static + Fn() -> i64) -> Test {
    Rc::new(func)
}
Aplet123
  • 33,825
  • 1
  • 29
  • 55
3

A generic type alias works, but a concrete one fails:

use std::rc::Rc;

type MyGenericRc<T> = Rc<T>;

// works
fn construct_with_generic_alias() -> MyGenericRc<dyn Fn() -> i64> {
    MyGenericRc::new(|| 42)
}

type MyConcreteRc = Rc<dyn Fn() -> i64>;

// fails
fn construct_with_concrete_alias() -> MyConcreteRc {
    MyConcreteRc::new(|| 42)
}

When you call MyGenericRc::new(<closure>) the compiler picks Rc::<closure type>::new(<closure>) as the concrete function implementation to use, which works since Rc::new requires a Sized argument and every anonymous closure type is Sized. However, after that function is called the compiler performs an unsizing coercion to transform the returned Rc<anonymous closure type> into an Rc<dyn Fn() -> i64>.

When you call MyConcreteRc::new(<closure>) you don't let the compiler decide which Rc::new implementation to use, you tell it that it must use Rc::<dyn Fn() -> i64>::new but it's impossible to satisfy that implementation's type requirements because new expects a Sized argument but dyn Fn() -> i64 is !Sized, hence the compile error message you are seeing.

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98