Downcasting "error" prevents me from calling functions

In theory the below code should work however inside the run function at the if let statement when trying to downcast a box holding a system trait object into a box holding a function trait object it fails. And I swear I had a similar implementation running earlier.

use std::any::Any;

trait System: 'static {
    fn as_any(&self) -> &dyn Any;
}

impl<T> System for T
where
    T: Fn() + 'static,
{
    fn as_any(&self) -> &dyn Any {
        self
    }
}

struct App {
    systems: Vec<Box<dyn System>>,
}

impl App {
    fn new() -> Self {
        Self {
            systems: vec![],
        }
    }
    
    fn add_systems(&mut self, systems: Vec<impl System>) -> &mut Self {
        self.systems.extend(systems.into_iter().map(|system| Box::new(system) as Box<dyn System>));
        self
    }

    fn run(&self) {
        self.systems.iter().for_each(|system| {
            if let Some(system) = system.as_any().downcast_ref::<Box<dyn Fn()>>() {
                system();
            }
        });
    }
}

fn greeting() {
    println!("hello, world!");
}

fn farewell() {
    println!("goodbye, world!");
}

fn main() {

    App::new()
        .add_systems(vec![greeting, farewell])
        .run();
}
1 Like

Here:

    App::new()
        .add_systems(vec![greeting, farewell])
        .run();

greeting and farewell get coerced to fn() function pointers. Then here:

            if let Some(system) = system.as_any().downcast_ref::<Box<dyn Fn()>>() {

for downcast_ref, you need to try to downcast what turned into dyn System, not the Box; and as per the above, that's a fn().

Thus:

            if let Some(system) = system.as_any().downcast_ref::<fn()>() {
2 Likes

Please always include the full compiler error when asking for help. It saves tons of time for the person reading and trying to answer. The full error is the one output by cargo in the terminal, not the partial errors sometimes shown by IDEs. Thanks.

2 Likes

It's possible you were coercing a Box<Box<dyn Fn()>> to a Box<dyn System>.

1 Like

Sorry for the confusion but there was no compiler error. It ran I just wasn't getting the output I expected.

1 Like

Thanks, that was my misunderstanding.

1 Like

I also have this macro to create, add systems, and run the app for you as shown below

macro_rules! app {
    ( ( $($system:expr),* ) ) => {{
        let mut systems: Vec<_> = Vec::new();
        $(
            systems.push($system);
        )*

        App::new()
            .add_systems(systems)
            .run();
    }}
}

and with this come two questions. Firstly, as far as my knowledge goes theres no way to implement trait bounds on declerative macros correct? And secondly, when I call the macro as I'll demonstrate in a second you pass in a tuple of systems but unlike directly calling the add systems function you need to specify the functions as function pointers. Why is that?

fn main() {
    app! {
        (greeting as fn(), farewell as fn())
    }
}

Nevermind its just the way rusts type inference works if I change this line let mut systems: Vec<_> = Vec::new(); to let mut systems: Vec<fn()> = Vec::new(); it works.

I don't understand what you mean.

You found a workaround, but here's what I wrote in the meanwhile.

This fails...

fn main() {
    let mut v = vec![];
    v.push(greeting); // L3
    v.push(farewell); // L4
}

...because each function has it's own unique function item type (similar to how closures have unique types). So on L3, there's nothing to make the compiler think you didn't want v to be a Vec<${function item type of greeting}>.

Whereas here:

let v = vec![greeting, farewell];
// Expands to approximately
let v = <[_]>::into_vec(Box::new([greeting, farewell]));

having both function items in an array means they must be the same type, and due to this the compiler decides to coerce them to function pointers. (Probably this is a special case for function items.)

Sorry it took me so long to get back to you @quinedot I've been messing around with the code for a few hours learning how to make macros. But thats besides the point, what I meant when saying this was the functions I pass to the macro like so:

fn greeting() {
    println!("hello, world!");
}

fn farewell(_: &str) {
    println!("goodbye, world!");
}

fn main() {
    app! {
        (greeting, farewell)
    }
}

should produce a compile time error stating that they don't implement the system trait such as

fn farewell(_: &str) {
    println!("goodbye, world!");
}

put this doesn't happen and instead with my solution I gave earlier of specifying the type of the vector you get this compile time error

mismatched types
expected fn pointer `fn()`
      found fn item `for<'a> fn(&'a str) {farewell}`

any other solution at least that I've tried that does result in the error I want or at least an error closer to what I want does so inside the macro rules block. One such solution being:

#[macro_export]
macro_rules! app {
    ( ($($system:expr),*) ) => {{
        let mut systems: Vec<Box<dyn System>> = Vec::new();
        $(
            systems.push(Box::new($system));
        )*

        App::new()
            .add_systems(systems)
            .run();
    }}
}

and you get this error

the trait bound `for<'a> fn(&'a str): temp::System` is not satisfied
the trait `temp::System` is implemented for fn pointer `fn()`
required for the cast from `Box<for<'a> fn(&'a str)>` to `Box<(dyn temp::System + 'static)>`

on this line

systems.push(Box::new($system));

so my question to you and anybody else who is willing to help is there something right in front of me that I'm missing? Whats the solution or is there even one?

No, there's no direct way to put a trait bound on a macro's input token. But you can do some indirect checks like...

fn _trait_check<T: ?Sized + System>(_: &T) {}
macro_rules! eg {
    ($sys:ident) => {
        _trait_check(&$sys);
        // ...
    }
}

If you think that error is better.

So today really was just not my day @quinedot because the macro and compiler time errors have been working correctly from the start. Below is the working implementation if your curious.
Filename: src/lib.rs

use std::any::Any;

pub trait System: Fn() + 'static {
    fn as_any(&self) -> &dyn Any;
}

impl<T> System for T
where
    T: Fn() + 'static,
{
    fn as_any(&self) -> &dyn Any {
        self
    }
}

pub struct App {
    systems: Vec<Box<dyn System>>,
}

impl App {
    pub fn new() -> Self {
        Self {
            systems: vec![],
        }
    }

    pub fn add_systems(&mut self, systems: Vec<fn()>) -> &mut Self {
        self.systems.extend(systems.into_iter().map(|system| Box::new(system) as Box<dyn System>));
        self
    }

    pub fn run(&self) {
        self.systems.iter().for_each(|system| {
            if let Some(system) = system.as_any().downcast_ref::<fn()>() {
                system();
            }
        });
    }
}

#[macro_export]
macro_rules! app {
    ( ($($system:expr),*) ) => {{
        let mut systems: Vec<fn()> = Vec::new();
        $(
            systems.push($system);
        )*

        App::new()
            .add_systems(systems)
            .run();
    }}
}

Filename: src/main.rs

use temp::*;

fn greeting() {
    println!("hello, world!");
}

fn farewell(_: &str) {
    println!("goodbye, world!");
}

fn main() {
    app! {
        (greeting, farewell)
    }
}

when using the declarative macro it provides the following error

mismatched types
expected fn pointer `fn()`
      found fn item `for<'a> fn(&'a str) {farewell}`

which is the exactly the same if I change the main function to not use the macro and instead use the associated constructor function and corresponding method calls
Filename: src/main.rs

fn main() {
    App::new()
        .add_systems(vec![greeting, farewell])
        .run();
}
mismatched types
expected fn pointer `fn()`
      found fn item `for<'a> fn(&'a str) {farewell}`

So sorry for the confusion and all your help. And as I've already marked this thread as solved I will no longer continue to bump said thread.