Introductions

Note: this book contains runnable examples. You can click the play button to see their output!

What is this book about?

Several rust libraries use a pattern something like this (in this example, bevy):

fn main() {
    App::new()
        .add_system(foo)
        .add_system(bar)
        .run();
}

fn foo(query: Query<Foo, With<Bar>>) {
    // some code
}

fn bar(query: Query<Bar, Without<Foo>>) {
    // some other code
}

Most users can intuitively grasp that this causes the app to automatically call the systems foo and bar once per frame, but very few are able to easily figure out how this is possible. This book aims to explain how this works, starting from scratch.

What is Dependency Injection?

Dependency injection is a needlessly complicated way to phrase "asking for things instead of providing them".

An easy example of dependency injection would be Iterator::map(); you provide a function/closure which asks for an item to map and maps it, and the iterator itself "injects" that "dependency".

In this case we're mimicking what Bevy Engine does. An App is created, provided with System's, and those System's are called automatically. System's have various parameters which are automatically known and provided by the App. The parameters are the dependencies, and the app is "injecting" them. Dependency injection is a useful pattern, and most people have probably used it at least somewhere even if they don't know it by name.

Is this book exclusively about rust and bevy?

About rust: Yes, but the techniques within can be applied to other languages if those languages have the features to support it.

About bevy: Sort of. This book is heavily inspired by it, but these techniques can certainly be applied to other rust projects, and have already shown up before in libraries like axum. And overall the technique shown will be simpler than what bevy actually does.

How much rust do I need to know to understand this?

I'll try to aim to make this as easily understandable as possible, but understanding of traits, dyn Traits, tuples, a little bit of lifetimes, and other basic rust knowledge will likely be required.

Setting up the scheduler

In order to illustrate what's going on, we'll want a data structure that can store resources to be queried, our systems, and run them. We'll keep it extremely simple:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
use std::any::{Any, TypeId};
type StoredSystem = ();
struct Scheduler {
    systems: Vec<StoredSystem>,
    resources: HashMap<TypeId, Box<dyn Any>>,
}
}

The scheduler stores StoredSystem's (don't worry, we haven't defined those yet) and uses a basic TypeMap which can store one item of every type (provided that the item lives for 'static a.k.a. is not a borrow and does not include a borrow).

What about that StoredSystem? We'll get back to those later, for now just supply it with a dummy definition:

#![allow(unused)]
fn main() {
struct StoredSystem;
}

Defining a system

We need to define what a System is in our context.

From a design perspective, we already know we can't store borrowing types; so those aren't allowed to be parameters to systems. We can also just say we'll panic! if a system asks for a resource we don't actually have one of. Finally we don't have anything to do with return values, so we'll prohibit them. That makes the definition of a system pretty straightforward: any function that takes 'static parameters and returns (). Let's translate that to rust:

#![allow(unused)]
fn main() {
trait System<Input> {}

impl<F: FnMut()> System<()> for F {}

impl<F: FnMut(T1), T1: 'static> System<(T1,)> for F {}

// impl<F: ugh nevermind I'm bored
}

(We have to include the inputs as a type parameter on System for complicated type system reasons that we'll get back to later...)

Ok, so minor issue: rust doesn't have variadic generics like the... what, two other languages that have ever existed that do? Let's write a quick decl macro to simplify this.

(I'm sorry, I know it's unreadable and downright arcane to the uninitiated, but this is pretty much the de facto rust solution to variadic generics right now)

#![allow(unused)]
fn main() {
trait System<Input> {}
macro_rules! impl_system {
    (
        $( 
            $($params:ident),+
        )?
    ) => {
        impl<
            F: FnMut(
                $( $($params),+ )?
            ) 
            $(, $($params: 'static),+ )?
        > 
        System<( 
            $( $($params,)+ )? 
        )> for F {}
    }
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);
}

We'll stop there for simplicity, but now it's a lot less typing to add more max params. We'll have a trick later to have unlimited params, but we'll still want to impl a good amount for syntax reasons.

Ok, cool, but this is useless. How can we have one function signature that can call any of these systems? We need to expose some way to flatten our input, give every system one parameter that can satisfy all of their requirements. How can we do that...?

How about this?

#![allow(unused)]
fn main() {
use std::collections::HashMap;
use std::any::{Any, TypeId};
trait System<Input> {
    fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}
}

Then this run function just needs to pull the resources out and we can wrap the actual call behind it!

Some (hacky macro) boilerplate later:

#![allow(unused)]
fn main() {
macro_rules! impl_system {
    (
        $( 
            $($params:ident),+
        )?
    ) => {
        #[allow(non_snake_case, unused)]
        impl<
            F: FnMut(
                $( $($params),+ )?
            ) 
            $(, $($params: 'static),+ )?
        > 
        System<( 
            $( $($params,)+ )? 
        )> for F {
            fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
                $($(
                    let $params = *resources.remove(&TypeId::of::<$params>()).unwrap().downcast::<$params>().unwrap();
                )+)?

                (self)(
                    $($($params),+)?
                );
            }
        }
    }
}
}

Spicy sidenote here: this does permanently remove the resources from the resource store on call. We'll get back to that later, just use the scheduler no more than once for now, or refill the resources after each run.

Ok, so we've implemented a trait so that we can call some functions without actually knowing their params. Mostly. The trait is still parameterized with that associated type, so we can't just Box<dyn System>. Let's make a type erased wrapper:

use std::collections::HashMap;
use std::any::{Any, TypeId};
trait System<Input> {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}
trait ErasedSystem {
    fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

impl<S: System<I>, I> ErasedSystem for S {
    fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
        <Self as System<I>>::run(self);
    }
}

Oops, that complicated type system stuff is back:

error[E0207]: the type parameter I is not constrained by the impl trait, self type, or predicates

I'll save you the trouble of trying to figure out what this really means: Any given type can implement multiple traits. FnMut(T) and FnMut(T, U) are different traits. Therefore a type can have multiple function implementations, and we're not explicitly selecting one. Now, we don't have any fancy future type system stuff like specialization (which might not help this situation I'm not sure), but we do have structs. While F can implement multiple FnMut traits, if we wrap F in a struct then that struct can "select" a specific implementation; the implementation is whichever matches the struct's generic parameters, which only one implementation can do. We'll call the struct FunctionSystem:

#![allow(unused)]
fn main() {
use std::marker::PhantomData;
struct FunctionSystem<Input, F> {
    f: F,
    // we need a marker because otherwise we're not using `Input`.
    // fn() -> Input is chosen because just using Input would not be `Send` + `Sync`,
    // but the fnptr is always `Send` + `Sync`.
    marker: PhantomData<fn() -> Input>,
}
}

Now let's move System from being on the function itself to FunctionSystem:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::marker::PhantomData;
struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}
trait System {
    fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

macro_rules! impl_system {
    (
        $( 
            $($params:ident),+
        )?
    ) => {
        #[allow(non_snake_case, unused)]
        impl<
            F: FnMut(
                $( $($params),+ )?
            ) 
            $(, $($params: 'static),+ )?
        > System for FunctionSystem<($( $($params,)+ )?), F> {
            fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
                $($(
                    let $params = *resources.remove(&TypeId::of::<$params>()).unwrap().downcast::<$params>().unwrap();
                )+)?

                (self.f)(
                    $($($params),+)?
                );
            }
        }
    }
}
}

Now that System takes no associated types or generic parameters, we can box it easily:

#![allow(unused)]
fn main() {
trait System {}
type StoredSystem = Box<dyn System>;
}

We'll also want to be able to convert FnMut(...) to a system easily instead of manually wrapping:

#![allow(unused)]
fn main() {
use std::marker::PhantomData;
trait System {}
struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}
impl<I, F> System for FunctionSystem<I, F> {}
trait IntoSystem<Input> {
    type System: System;

    fn into_system(self) -> Self::System;
}

// Example output:
// impl<F: FnMut(T1), T1> IntoSystem<(T1,)> for F {
//     type System = FunctionSystem<(T1,), Self>;

//     fn into_system(self) -> Self::System {
//         FunctionSystem {
//             f: self,
//             marker: Default::default(),
//         }
//     }
// }

macro_rules! impl_into_system {
    (
        $($(
                $params:ident
        ),+)?
    ) => {
        impl<F: FnMut($($($params),+)?) $(, $($params: 'static),+ )?> IntoSystem<( $($($params,)+)? )> for F {
            type System = FunctionSystem<( $($($params,)+)? ), Self>;

            fn into_system(self) -> Self::System {
                FunctionSystem {
                    f: self,
                    marker: Default::default(),
                }
            }
        }
    }
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);
}

Some helpers on Scheduler:

#![allow(unused)]
fn main() {
use std::any::{Any, TypeId};
use std::collections::HashMap;
trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}
type StoredSystem = Box<dyn System>;
struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}
trait IntoSystem<Input> {
   type System: System;

   fn into_system(self) -> Self::System;
}
impl Scheduler {
    pub fn run(&mut self) {
        for system in self.systems.iter_mut() {
            system.run(&mut self.resources);
        }
    }

    pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
        self.systems.push(Box::new(system.into_system()));
    }

    pub fn add_resource<R: 'static>(&mut self, res: R) {
        self.resources.insert(TypeId::of::<R>(), Box::new(res));
    }
}
}

All together now!

use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::marker::PhantomData;
struct FunctionSystem<Input, F> {
    f: F,
    marker: PhantomData<fn() -> Input>,
}

trait System {
    fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

macro_rules! impl_system {
    (
        $( 
            $($params:ident),+
        )?
    ) => {
        #[allow(non_snake_case, unused)]
        impl<
            F: FnMut(
                $( $($params),+ )?
            ) 
            $(, $($params: 'static),+ )?
        > System for FunctionSystem<($( $($params,)+ )?), F> {
            fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
                $($(
                    let $params = *resources.remove(&TypeId::of::<$params>()).unwrap().downcast::<$params>().unwrap();
                )+)?

                (self.f)(
                    $($($params),+)?
                );
            }
        }
    }
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
    type System: System;

    fn into_system(self) -> Self::System;
}

macro_rules! impl_into_system {
    (
        $($(
                $params:ident
        ),+)?
    ) => {
        impl<F: FnMut($($($params),+)?) $(, $($params: 'static),+ )?> IntoSystem<( $($($params,)+)? )> for F {
            type System = FunctionSystem<( $($($params,)+)? ), Self>;

            fn into_system(self) -> Self::System {
                FunctionSystem {
                    f: self,
                    marker: Default::default(),
                }
            }
        }
    }
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

struct Scheduler {
    systems: Vec<StoredSystem>,
    resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
    pub fn run(&mut self) {
        for system in self.systems.iter_mut() {
            system.run(&mut self.resources);
        }
    }

    pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
        self.systems.push(Box::new(system.into_system()));
    }

    pub fn add_resource<R: 'static>(&mut self, res: R) {
        self.resources.insert(TypeId::of::<R>(), Box::new(res));
    }
}

fn main() {
    let mut scheduler = Scheduler {
        systems: vec![],
        resources: HashMap::default(),
    };

    scheduler.add_system(foo);
    scheduler.add_resource(12i32);

    scheduler.run();
}

fn foo(int: i32) {
    println!("int! {int}");
}

It prints int! 12 like we want! And the user would never actually see their function get called. Mission success?

Yes, but there's obviously some rough edges. It permanently removes resources from the store each run, we have a max limit on parameters, etc, etc. We can do better, and I'll come back to this later to add some more.

Passing references

Yes, but there's obviously some rough edges. It permanently removes resources from the store each run, we have a max limit on parameters, etc, etc. We can do better, and I'll come back to this later to add some more.

Having gotten the basic architecture working, it's time to make some refinements. In this chapter we'll be focusing on two issues: The maximum limit on system parameters, and the fact that it "self destructs" every run by consuming resources. The latter will enable the former, so we'll start with allowing borrows.

First let's switch from owned values to borrowed ones, and see what we can do from there:

macro_rules! impl_system {
    (
        $(
            $($params:ident),+
        )?
    ) => {
        #[allow(non_snake_case)]
        #[allow(unused)]
        impl<
            F: FnMut(
                $( $(& $params),+ )?
            )
            $(, $($params: 'static),+ )?
        > System for FunctionSystem<($( $($params,)+ )?), F> {
            fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
                $($(
                    let $params = resources.get(&TypeId::of::<$params>()).unwrap().downcast_ref::<$params>().unwrap();
                )+)?

                (self.f)(
                    $($($params),+)?
                );
            }
        }
    }
}

macro_rules! impl_into_system {
    (
        $($(
                $params:ident
        ),+)?
    ) => {
        impl<F: FnMut($($(& $params),+)?) $(, $($params: 'static),+ )?> IntoSystem<( $($($params,)+)? )> for F {
            type System = FunctionSystem<( $($($params,)+)? ), Self>;

            fn into_system(self) -> Self::System {
                FunctionSystem {
                    f: self,
                    marker: Default::default(),
                }
            }
        }
    }
}
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::marker::PhantomData;

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
   type System: System;

   fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

   pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
       self.systems.push(Box::new(system.into_system()));
   }

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}

fn main() {
   let mut scheduler = Scheduler {
       systems: vec![],
       resources: HashMap::default(),
   };

   scheduler.add_system(foo);
   scheduler.add_resource(12i32);

   scheduler.run();
}

fn foo(int: &i32) {
    println!("int! {int}");
}

This works, but there's a pretty obvious problem:

use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::marker::PhantomData;

macro_rules! impl_system {
   (
       $(
           $($params:ident),+
       )?
   ) => {
       #[allow(non_snake_case)]
       #[allow(unused)]
       impl<
           F: FnMut(
               $( $(& $params),+ )?
           )
           $(, $($params: 'static),+ )?
       > System for FunctionSystem<($( $($params,)+ )?), F> {
           fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
               $($(
                   let $params = resources.get(&TypeId::of::<$params>()).unwrap().downcast_ref::<$params>().unwrap();
               )+)?

               (self.f)(
                   $($($params),+)?
               );
           }
       }
   }
}

macro_rules! impl_into_system {
   (
       $($(
               $params:ident
       ),+)?
   ) => {
       impl<F: FnMut($($(& $params),+)?) $(, $($params: 'static),+ )?> IntoSystem<( $($($params,)+)? )> for F {
           type System = FunctionSystem<( $($($params,)+)? ), Self>;

           fn into_system(self) -> Self::System {
               FunctionSystem {
                   f: self,
                   marker: Default::default(),
               }
           }
       }
   }
}

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
   type System: System;

   fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

   pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
       self.systems.push(Box::new(system.into_system()));
   }

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}

fn main() {
   let mut scheduler = Scheduler {
       systems: vec![],
       resources: HashMap::default(),
   };

   scheduler.add_system(foo);
   scheduler.add_resource(12i32);

   scheduler.run();
}
fn foo(int: i32) {
    println!("int! {int}");
}

error[E0277]: the trait bound fn(i32) {foo}: IntoSystem<_> is not satisfied

That's not great. It'd be nice to be able to still consume resources if desired- or more likely, use mutable references. We could change it to mutable references, but then we can't use immutable references. And trying to manually implement all three would be a bit of a combinatorial explosion- every permutation of owned/&/&mut leads to something like 3^8 implementations for the 8 parameter version alone. Not exactly reasonable, even with macros.

Let's try something else; let's abstract over all possible system parameters.

trait SystemParam {
    fn retrieve(resources: &mut HashMap<TypeId, Box<dyn Any>>) -> Self;
}

struct Res<'a, T: 'static> {
    value: &'a T,
}

impl<'a, T: 'static> SystemParam for Res<'a, T> {
    fn retrieve(resources: &mut HashMap<TypeId, Box<dyn Any>>) -> Self {
        let value = resources.get(&TypeId::of::<T>()).unwrap().downcast_ref::<T>().unwrap();
        Res { value }
    }
}

struct ResMut<'a, T: 'static> {
    value: &'a mut T,
}

impl<'a, T: 'static> SystemParam for ResMut<'a, T> {
    fn retrieve(resources: &mut HashMap<TypeId, Box<dyn Any>>) -> Self {
        let value = resources.get_mut(&TypeId::of::<T>()).unwrap().downcast_mut::<T>().unwrap();
        ResMut { value }
    }
}

struct ResOwned<T: 'static> {
    value: T
}

impl<T: 'static> SystemParam for ResOwned<T> {
    fn retrieve(resources: &mut HashMap<TypeId, Box<dyn Any>>) -> Self {
        let value = *resources.remove(&TypeId::of::<T>()).unwrap().downcast::<T>().unwrap();
        ResOwned { value }
    }
}

SystemParam provides the retrieve function which is where our logic for gethering resources lives. Conveniently, this simplifies the (now once again outdated) macro implementation. Res/ResMut/ResOwned map to &/&mut/owned respectively. They also closely resemble some of bevy's own SystemParams.

Great, now let's try to compile and-

error: lifetime may not live long enough

oh wow lifetime errors my favorite

This seems like an easy fix at first...

// The modification is the same for ResMut/Owned
impl<'a, T: 'static> SystemParam for Res<'a, T> {
    fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
        let value = resources.get(&TypeId::of::<T>()).unwrap().downcast_ref::<T>().unwrap();
        Res { value }
    }
}

But this changes the function signature, so we need a lifetime in SystemParam

trait SystemParam<'a> {
    fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self;
}

(which of course infects the macros AGAIN (hence why I haven't updated them yet))

This leads to yet another lifetime error in implementing systems, as they try to pass in a &'_ mut HashMap... rather than &'a mut HashMap....

trait System<'a> {
    fn run(&mut self, resources: &'a mut HashMap<TypeId, Box<dyn Any>>);
}

Which then impacts IntoSystem...

trait IntoSystem<'a, Input> {
    type System: System<'a>;

    fn into_system(self) -> Self::System;
}

AND StoredSystem...

type StoredSystem = Box<dyn for<'a> System<'a>>;

And finally add_system

#![allow(unused)]
fn main() {
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::marker::PhantomData;

trait SystemParam<'a> {
   fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self;
}

struct Res<'a, T: 'static> {
   value: &'a T,
}

impl<'a, T: 'static> SystemParam<'a> for Res<'a, T> {
   fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
       let value = resources.get(&TypeId::of::<T>()).unwrap().downcast_ref::<T>().unwrap();
       Res { value }
   }
}

struct ResMut<'a, T: 'static> {
   value: &'a mut T,
}

impl<'a, T: 'static> SystemParam<'a> for ResMut<'a, T> {
   fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
       let value = resources.get_mut(&TypeId::of::<T>()).unwrap().downcast_mut::<T>().unwrap();
       ResMut { value }
   }
}

struct ResOwned<T: 'static> {
   value: T
}

impl<T: 'static> SystemParam<'static> for ResOwned<T> {
   fn retrieve(resources: &'static mut HashMap<TypeId, Box<dyn Any>>) -> Self {
       let value = *resources.remove(&TypeId::of::<T>()).unwrap().downcast::<T>().unwrap();
       ResOwned { value }
   }
}

macro_rules! impl_system {
   (
       $(
           $($params:ident),+
       )?
   ) => {
       #[allow(non_snake_case)]
       #[allow(unused)]
       impl<
           'a,
           F: FnMut(
               $( $($params),+ )?
           )
           $(, $($params: SystemParam<'a>),+ )?
       > System<'a> for FunctionSystem<($( $($params,)+ )?), F> {
           fn run(&mut self, resources: &'a mut HashMap<TypeId, Box<dyn Any>>) {
               $($(
                   let $params = $params::retrieve(resources);
               )+)?

               (self.f)(
                   $($($params),+)?
               );
           }
       }
   }
}

macro_rules! impl_into_system {
   (
       $($(
               $params:ident
       ),+)?
   ) => {
       impl<'a, F: FnMut($($($params),+)?) $(, $($params: SystemParam<'a>),+ )?> IntoSystem<'a, ( $($($params,)+)? )> for F {
           type System = FunctionSystem<( $($($params,)+)? ), Self>;

           fn into_system(self) -> Self::System {
               FunctionSystem {
                   f: self,
                   marker: Default::default(),
               }
           }
       }
   }
}

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System<'a> {
   fn run(&mut self, resources: &'a mut HashMap<TypeId, Box<dyn Any>>);
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<'a, Input> {
   type System: System<'a>;

   fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn for<'a> System<'a>>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

pub fn add_system<I, S: for<'a> System<'a> + 'static>(&mut self, system: impl for<'a> IntoSystem<'a, I, System = S>) {
    self.systems.push(Box::new(system.into_system()));
}

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}
}

WHEW! Glad that's over. Now it's time for a real error. One that isn't just us needing to slap annotations everywhere.

error[E0499]: cannot borrow *resources as mutable more than once at a time

Yep! We're mutably borrowing resources multiple times for variants with > 1 parameter. How do we solve this, using all the clever tools rust provides to create a safe, powerful solution-

use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::marker::PhantomData;

struct Res<'a, T: 'static> {
   value: &'a T,
}

macro_rules! impl_system {
   (
       $(
           $($params:ident),+
       )?
   ) => {
       #[allow(non_snake_case)]
       #[allow(unused)]
       impl<
           'a,
           F: FnMut(
               $( $($params),+ )?
           )
           $(, $($params: SystemParam<'a>),+ )?
       > System<'a> for FunctionSystem<($( $($params,)+ )?), F> {
           fn run(&mut self, resources: &'a mut HashMap<TypeId, Box<dyn Any>>) {
               $($(
                   let $params = $params::retrieve(resources);
               )+)?

               (self.f)(
                   $($($params),+)?
               );
           }
       }
   }
}

macro_rules! impl_into_system {
   (
       $($(
               $params:ident
       ),+)?
   ) => {
       impl<'a, F: FnMut($($($params),+)?) $(, $($params: SystemParam<'a>),+ )?> IntoSystem<'a, ( $($($params,)+)? )> for F {
           type System = FunctionSystem<( $($($params,)+)? ), Self>;

           fn into_system(self) -> Self::System {
               FunctionSystem {
                   f: self,
                   marker: Default::default(),
               }
           }
       }
   }
}

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System<'a> {
   fn run(&mut self, resources: &'a mut HashMap<TypeId, Box<dyn Any>>);
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<'a, Input> {
   type System: System<'a>;

   fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn for<'a> System<'a>>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

   pub fn add_system<I, S: for<'a> System<'a> + 'static>(&mut self, system: impl for<'a> IntoSystem<'a, I, System = S>) {
       self.systems.push(Box::new(system.into_system()));
   }

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}

fn main() {
   let mut scheduler = Scheduler {
       systems: vec![],
       resources: HashMap::default(),
   };

   scheduler.add_system(foo);
   scheduler.add_resource(12i32);

   scheduler.run();
}

fn foo(int: Res<i32>) {
   println!("int! {}", int.value);    
}
trait SystemParam<'a> {
    fn retrieve(resources: &'a HashMap<TypeId, Box<dyn Any>>) -> Self;
}

impl<'a, T: 'static> SystemParam<'a> for Res<'a, T> {
    fn retrieve(resources: &'a HashMap<TypeId, Box<dyn Any>>) -> Self {
        let value = resources.get(&TypeId::of::<T>()).unwrap().downcast_ref::<T>().unwrap();
        Res { value }
    }
}

// struct ResMut<'a, T: 'static> {
//     value: &'a mut T,
// }

// impl<'a, T: 'static> SystemParam for ResMut<'a, T> {
//     fn retrieve(resources: &mut HashMap<TypeId, Box<dyn Any>>) -> Self {
//         let value = resources.get_mut(&TypeId::of::<T>()).unwrap().downcast_mut::<T>().unwrap();
//         ResMut { value }
//     }
// }

// struct ResOwned<T: 'static> {
//     value: T
// }

// impl<T: 'static> SystemParam for ResOwned<T> {
//     fn retrieve(resources: &mut HashMap<TypeId, Box<dyn Any>>) -> Self {
//         let value = *resources.remove(&TypeId::of::<T>()).unwrap().downcast::<T>().unwrap();
//         ResOwned { value }
//     }
// }

We'll burn that bridge when we get to it, I don't have the time for interior mutability or unsafe shenanigans right now. Because unfortunately that lifetime stuff is back.

error: implementation of System is not general enough

We can't actually pass any existing system to add_system, because it requires that the system implement both System and IntoSystem for all lifetimes. (That's what that for<'a> bit means). It doesn't, it's only implemented for the lifetime of its parameter, so that won't work. And if that won't work, then we can't box it like this either, so it looks like we'll need to go back to the drawing board. Why not take a look at how bevy approaches this?

impl<Out, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<(), Out, ($($param,)*), ()> for Func
        where
        for <'a> &'a mut Func:
                FnMut($($param),*) -> Out +
                FnMut($(SystemParamItem<$param>),*) -> Out, Out: 'static

How interesting... and what is SystemParamItem?

/// Shorthand way of accessing the associated type [`SystemParam::Item`] for a given [`SystemParam`].
pub type SystemParamItem<'w, 's, P> = <P as SystemParam>::Item<'w, 's>;

Ah, "easy". So SystemParam has a GAT called Item which is the same as the SystemParam, but with a new lifetime. They can take the function with some irrelevant lifetime, and then give it a new lifetime of the passed in resources. And while the type alias makes it shorter, I'm going to go without it to illustrate what it really means. Very complicated, and very clever. Let's do it!

trait SystemParam {
    type Item<'new>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r>;
}

impl<'res, T: 'static> SystemParam for Res<'res, T> {
    type Item<'new> = Res<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
        Res { value: resources.get(&TypeId::of::<T>()).unwrap().downcast_ref().unwrap() }
    }
}

macro_rules! impl_system {
    (
        $($params:ident),*
    ) => {
        #[allow(non_snake_case)]
        #[allow(unused)]
        impl<F, $($params: SystemParam),*> System for FunctionSystem<($($params,)*), F> 
            where
                for<'a, 'b> &'a mut F: 
                    FnMut( $($params),* ) + 
                    FnMut( $(<$params as SystemParam>::Item<'b>),* )
        {
            fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
                fn call_inner<$($params),*>(
                    mut f: impl FnMut($($params),*),
                    $($params: $params),*
                ) {
                    f($($params),*)
                }

                $(
                    let $params = $params::retrieve(resources);
                )*

                call_inner(&mut self.f, $($params),*)
            }
        }
    }
}

macro_rules! impl_into_system {
    (
        $($params:ident),*
    ) => {
        impl<F, $($params: SystemParam),*> IntoSystem<($($params,)*)> for F 
            where
                for<'a, 'b> &'a mut F: 
                    FnMut( $($params),* ) + 
                    FnMut( $(<$params as SystemParam>::Item<'b>),* )
        {
            type System = FunctionSystem<($($params,)*), Self>;

            fn into_system(self) -> Self::System {
                FunctionSystem {
                    f: self,
                    marker: Default::default(),
                }
            }
        }
    }
}
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::marker::PhantomData;

struct Res<'a, T: 'static> {
   value: &'a T,
}

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
   type System: System;

   fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

   pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
       self.systems.push(Box::new(system.into_system()));
   }

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}

fn main() {
   let mut scheduler = Scheduler {
       systems: vec![],
       resources: HashMap::default(),
   };

   scheduler.add_system(foo);
   scheduler.add_resource(12i32);

   scheduler.run();
}

fn foo(int: Res<i32>) {
   println!("int! {}", int.value);    
}

(The call_inner bit is necessary to tell rust which function impl to call, it gets a bit confused otherwise.)

And this works! Perfectly! No weird errors, we've finally solved the ability to pass &/&mut/own... right, we put that off for a bit. But we have the infrastructure!

And this infrastructure lends itself perfectly to allowing unlimited parameters, which we'll do next.

More parameters

Now that we have SystemParam in place, it'll be easy to expand this to work with unlimited parameters. We just need one crucial idea: what if a tuple of SystemParam is, itself, a SystemParam? Let's implement:

use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::marker::PhantomData;

trait SystemParam {
   type Item<'new>;

   fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r>;
}

struct Res<'a, T: 'static> {
   value: &'a T,
}

impl<'res, T: 'static> SystemParam for Res<'res, T> {
   type Item<'new> = Res<'new, T>;

   fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
       Res { value: resources.get(&TypeId::of::<T>()).unwrap().downcast_ref().unwrap() }
   }
}

// struct ResMut<'a, T: 'static> {
//     value: &'a mut T,
// }

// impl<'a, T: 'static> SystemParam<'a> for ResMut<'a, T> {
//     fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
//         let value = resources.get_mut(&TypeId::of::<T>()).unwrap().downcast_mut::<T>().unwrap();
//         ResMut { value }
//     }
// }

// struct ResOwned<T: 'static> {
//     value: T
// }

// impl<'a, T: 'static> SystemParam<'a> for ResOwned<T> {
//     fn retrieve(resources: &'a mut HashMap<TypeId, Box<dyn Any>>) -> Self {
//         let value = *resources.remove(&TypeId::of::<T>()).unwrap().downcast::<T>().unwrap();
//         ResOwned { value }
//     }
// }

struct FunctionSystem<Input, F> {
   f: F,
   marker: PhantomData<fn() -> Input>,
}

trait System {
   fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}

macro_rules! impl_system {
   (
       $($params:ident),*
   ) => {
       #[allow(non_snake_case)]
       #[allow(unused)]
       impl<F, $($params: SystemParam),*> System for FunctionSystem<($($params,)*), F> 
           where
               for<'a, 'b> &'a mut F: 
                   FnMut( $($params),* ) + 
                   FnMut( $(<$params as SystemParam>::Item<'b>),* )
       {
           fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
               fn call_inner<$($params),*>(
                   mut f: impl FnMut($($params),*),
                   $($params: $params),*
               ) {
                   f($($params),*)
               }

               $(
                   let $params = $params::retrieve(resources);
               )*

               call_inner(&mut self.f, $($params),*)
           }
       }
   }
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
   type System: System;

   fn into_system(self) -> Self::System;
}

macro_rules! impl_into_system {
   (
       $($params:ident),*
   ) => {
       impl<F, $($params: SystemParam),*> IntoSystem<($($params,)*)> for F 
           where
               for<'a, 'b> &'a mut F: 
                   FnMut( $($params),* ) + 
                   FnMut( $(<$params as SystemParam>::Item<'b>),* )
       {
           type System = FunctionSystem<($($params,)*), Self>;

           fn into_system(self) -> Self::System {
               FunctionSystem {
                   f: self,
                   marker: Default::default(),
               }
           }
       }
   }
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

struct Scheduler {
   systems: Vec<StoredSystem>,
   resources: HashMap<TypeId, Box<dyn Any>>,
}

impl Scheduler {
   pub fn run(&mut self) {
       for system in self.systems.iter_mut() {
           system.run(&mut self.resources);
       }
   }

   pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
       self.systems.push(Box::new(system.into_system()));
   }

   pub fn add_resource<R: 'static>(&mut self, res: R) {
       self.resources.insert(TypeId::of::<R>(), Box::new(res));
   }
}

fn main() {
   let mut scheduler = Scheduler {
       systems: vec![],
       resources: HashMap::default(),
   };

   scheduler.add_system(foo);
   scheduler.add_resource(12i32);
   scheduler.add_resource(7u32);

   scheduler.run();
}
impl<T1: SystemParam, T2: SystemParam> SystemParam for (T1, T2) {
    type Item<'new> = (T1::Item<'new>, T2::Item<'new>);

    fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
        (
            T1::retrieve(resources),
            T2::retrieve(resources),
        )
    }
}

fn foo(int: (Res<i32>, Res<u32>)) {
    println!("int! {} uint! {}", int.0.value, int.1.value);
}

It just works!

Now you may wonder:

But this is only two items? This doesn't actually give us "unlimited" parameters, just slightly more?

But this is secretly sufficient to have unlimited parameters:

fn foo(int: (Res<One>, (Res<Two>, (Res<Three>, (Res<Four>))))) {
    // ...
}

And so on. As I hinted at in chapter 1, there's a syntax cost to this, but it's alleviated by implementing up to 16-tuples, so it's macro time:

macro_rules! impl_system_param {
    (
        $($params:ident),*
    ) => {
        #[allow(unused)]
        impl<$($params: SystemParam),*> SystemParam for ($($params,)*) {
            type Item<'new> = ($($params::Item<'new>,)*);

            fn retrieve<'r>(resources: &'r HashMap<TypeId, Box<dyn Any>>) -> Self::Item<'r> {
                (
                    $($params::retrieve(resources),)*
                )
            }
        }
    }
}

impl_system_param!();
impl_system_param!(T1);
impl_system_param!(T1, T2);
impl_system_param!(T1, T2, T3);
// and so on

And that's it, actually. We can nest parameters indefinitely to pass infinite parameters. Next time we'll go back to that aliasing issue and figure out how to get disjoint mutable access to resources.

The easy way out

So, our goal is to get disjoint mutable access to resources in the world and provide them to systems that are run strictly serially (singlethreaded) (!!!). This isn't easy, because the borrow checker has a hard time when you try to get multiple mutable references inside a data structure (such as our hashmap) at the same time.

Luckily for us, rust provides some escape hatches. First I'll cover the safe, easier way, then the fun way.

The primary tool we're going to use for the "easy way" is a concept called

Interior Mutability:tm:

Very scary sounding, but actually very simple. Interior mutability simply means there exists a function for this type roughly like fn(&self) -> &mut Self::Inner. This, of course, seems to violate a fundamental rule of the borrow checker, that you cannot mutate from an immutable reference.

But sometimes you need to do that, so rust provides the UnsafeCell type. Then, safe types were built on top of it for our convenience. In this case, we're going to use a neat little type called RefCell.

RefCell is a type that allows safe interior mutability by checking at runtime if it's being accessed correctly. Pretend it looks like this on the inside (this is pseudocode which will not compile for the sake of being clearer to read):

enum Borrow {
    None,
    Immutable(NonZeroUsize),
    Mutable(NonZeroUsize),
}

struct RefCell<T> {
    cell: UnsafeCell<T>,
    borrows: Borrow,
}

Then when you attempt to borrow it:

// immutable
match &mut self.borrows {
    Borrow::None => {
        self.borrows = Borrow::Immutable(1);
        unsafe { &*self.cell.get() }
    }
    Borrow::Immutable(x) => {
        *x += 1;
        unsafe { &*self.cell.get() }
    },
    Borrow::Mutable(_) => panic!(),
}

// mutable
match &mut self.borrows {
    Borrow::None => {
        self.borrows = Borrow::Mutable(1);
        unsafe { &mut *self.cell.get() }
    }
    Borrow::Immutable(_) => panic!(),
    Borrow::Mutable(_) => panic!(),
}

It increments reference counters whenever you borrow, or panics if you attempt to make invalid borrows like an immutable reference when a mutable reference exists.

Then, instead of returning &T or &mut T, it returns the special types Ref and RefMut, which are Deref<Target = T>. When these are dropped, they decrement the borrow counter.

It's like a runtime borrow checker! This is super useful but very critically: not threadsafe, as I alluded to above with heavy emphasis. A threadsafe alternative would be something like Mutex or RwLock, which have different semantics.

But we're not threading, so we're good!

First, let's make an observation:

#![allow(unused)]
fn main() {
let v = vec![1, 2];
let x = &mut v[0];
let y = &mut v[1];

println!("{x} {y}");
}

This doesn't compile, since we're violating the principle of mutability XOR aliasing. But this works:

#![allow(unused)]
fn main() {
use std::cell::RefCell;
let v = vec![
    RefCell::new(1), 
    RefCell::new(2),
];
let mut x = v[0].borrow_mut();
let mut y = v[1].borrow_mut();

*x += 1;
*y += 1;

println!("{x} {y}");
}

This should make at least some intuitive sense now, but to be more clear:

  1. .borrow_mut() takes &self, not &mut self
  2. Thus x and y are immutably borrowing from v
  3. x and y are of type RefMut, which can provide a mutable reference to its inner type (but they must be marked mut to get a mutable reference to them to do so)
  4. RefMut impls Display for T: Display and Deref<Target = T>, so we can basically use them as if they're &mut T

Now we should understand the tool well enough to put it to use.

Implementation

First let's redefine a few things:

  • Add RefCell into Schedule's resources (and also add default derive for convenience)
#[derive(Default)]
struct Scheduler {
    systems: Vec<StoredSystem>,
    resources: HashMap<TypeId, RefCell<Box<dyn Any>>>,
}
  • Wrap resources in RefCell in add_resource
impl Scheduler {
    pub fn run(&mut self) {
        for system in self.systems.iter_mut() {
            system.run(&mut self.resources);
        }
    }

    pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
        self.systems.push(Box::new(system.into_system()));
    }

    pub fn add_resource<R: 'static>(&mut self, res: R) {
        self.resources
            .insert(TypeId::of::<R>(), RefCell::new(Box::new(res)));
    }
}
  • Add RefCell to signature here
trait System {
    fn run(&mut self, resources: &mut HashMap<TypeId, RefCell<Box<dyn Any>>>);
}
  • And here
trait SystemParam {
    type Item<'new>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r>;
}
  • Res needs to store a Ref<Box<dyn Any>> now instead of &T, or the Ref will be dropped early
struct Res<'a, T: 'static> {
    value: Ref<'a, Box<dyn Any>>,
    _marker: PhantomData<&'a T>,
}
impl<T: 'static> Deref for Res<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.value.downcast_ref().unwrap()
    }
}
  • Add a .borrow() here to implement Res trivially
impl<'res, T: 'static> SystemParam for Res<'res, T> {
    type Item<'new> = Res<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r> {
        Res {
            value: resources.get(&TypeId::of::<T>()).unwrap().borrow(),
            _marker: PhantomData,
        }
    }
}

(also had to tweak the macros, but they're very noisy and are at the end)

And this gives us a functioning Res again! Now let's implement ResMut:

  • Define ResMut:
struct ResMut<'a, T: 'static> {
    value: RefMut<'a, Box<dyn Any>>,
    _marker: PhantomData<&'a mut T>,
}

impl<T: 'static> Deref for ResMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.value.downcast_ref().unwrap()
    }
}

impl<T: 'static> DerefMut for ResMut<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        self.value.downcast_mut().unwrap()
    }
}
  • Impl SystemParam for it:
impl<'res, T: 'static> SystemParam for ResMut<'res, T> {
    type Item<'new> = ResMut<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r> {
        ResMut {
            value: resources.get(&TypeId::of::<T>()).unwrap().borrow_mut(),
            _marker: PhantomData,
        }
    }
}

And there we go! We can now access multiple resources mutably from systems.

However, we can't actually add owned resources still- one might notice that bevy does not have anything like this anyway. If you wanted to accomplish this, one way would be to wrap the RefCell in resources with Option, and then you can use .take() to remove a resource from resources entirely to define SystemParam::retrieve for the owned resource. However this would be niche and error prone to use, so I'm not going to do it myself.

Final Product

use std::any::{Any, TypeId};
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};

macro_rules! impl_system {
    (
        $($params:ident),*
    ) => {
        #[allow(non_snake_case)]
        #[allow(unused)]
        impl<F, $($params: SystemParam),*> System for FunctionSystem<($($params,)*), F>
            where
                for<'a, 'b> &'a mut F:
                    FnMut( $($params),* ) +
                    FnMut( $(<$params as SystemParam>::Item<'b>),* )
        {
            fn run(&mut self, resources: &mut HashMap<TypeId, RefCell<Box<dyn Any>>>) {
                fn call_inner<$($params),*>(
                    mut f: impl FnMut($($params),*),
                    $($params: $params),*
                ) {
                    f($($params),*)
                }

                $(
                    let $params = $params::retrieve(resources);
                )*

                call_inner(&mut self.f, $($params),*)
            }
        }
    }
}

macro_rules! impl_into_system {
    (
        $($params:ident),*
    ) => {
        impl<F, $($params: SystemParam),*> IntoSystem<($($params,)*)> for F
            where
                for<'a, 'b> &'a mut F:
                    FnMut( $($params),* ) +
                    FnMut( $(<$params as SystemParam>::Item<'b>),* )
        {
            type System = FunctionSystem<($($params,)*), Self>;

            fn into_system(self) -> Self::System {
                FunctionSystem {
                    f: self,
                    marker: Default::default(),
                }
            }
        }
    }
}

trait SystemParam {
    type Item<'new>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r>;
}

impl<'res, T: 'static> SystemParam for Res<'res, T> {
    type Item<'new> = Res<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r> {
        Res {
            value: resources.get(&TypeId::of::<T>()).unwrap().borrow(),
            _marker: PhantomData,
        }
    }
}

impl<'res, T: 'static> SystemParam for ResMut<'res, T> {
    type Item<'new> = ResMut<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r> {
        ResMut {
            value: resources.get(&TypeId::of::<T>()).unwrap().borrow_mut(),
            _marker: PhantomData,
        }
    }
}

struct Res<'a, T: 'static> {
    value: Ref<'a, Box<dyn Any>>,
    _marker: PhantomData<&'a T>,
}

impl<T: 'static> Deref for Res<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.value.downcast_ref().unwrap()
    }
}

struct ResMut<'a, T: 'static> {
    value: RefMut<'a, Box<dyn Any>>,
    _marker: PhantomData<&'a mut T>,
}

impl<T: 'static> Deref for ResMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.value.downcast_ref().unwrap()
    }
}

impl<T: 'static> DerefMut for ResMut<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        self.value.downcast_mut().unwrap()
    }
}

struct FunctionSystem<Input, F> {
    f: F,
    marker: PhantomData<fn() -> Input>,
}

trait System {
    fn run(&mut self, resources: &mut HashMap<TypeId, RefCell<Box<dyn Any>>>);
}

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
    type System: System;

    fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

#[derive(Default)]
struct Scheduler {
    systems: Vec<StoredSystem>,
    resources: HashMap<TypeId, RefCell<Box<dyn Any>>>,
}

impl Scheduler {
    pub fn run(&mut self) {
        for system in self.systems.iter_mut() {
            system.run(&mut self.resources);
        }
    }

    pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
        self.systems.push(Box::new(system.into_system()));
    }

    pub fn add_resource<R: 'static>(&mut self, res: R) {
        self.resources
            .insert(TypeId::of::<R>(), RefCell::new(Box::new(res)));
    }
}

fn main() {
    let mut scheduler = Scheduler::default();
    scheduler.add_system(foo);
    scheduler.add_system(bar);
    scheduler.add_resource(12i32);
    scheduler.add_resource("Hello, world!");

    scheduler.run();
}

fn foo(mut int: ResMut<i32>) {
    *int += 1;
}

fn bar(statement: Res<&'static str>, num: Res<i32>) {
    assert_eq!(*num, 13);
    println!("{} My lucky number is: {}", *statement, *num);
}

Pretty cool! But this does have one sharp edge (if you run this, it will panic):

// ANCHOR: All
use std::any::{Any, TypeId};
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};

macro_rules! impl_system {
    (
        $($params:ident),*
    ) => {
        #[allow(non_snake_case)]
        #[allow(unused)]
        impl<F, $($params: SystemParam),*> System for FunctionSystem<($($params,)*), F>
            where
                for<'a, 'b> &'a mut F:
                    FnMut( $($params),* ) +
                    FnMut( $(<$params as SystemParam>::Item<'b>),* )
        {
            fn run(&mut self, resources: &mut HashMap<TypeId, RefCell<Box<dyn Any>>>) {
                fn call_inner<$($params),*>(
                    mut f: impl FnMut($($params),*),
                    $($params: $params),*
                ) {
                    f($($params),*)
                }

                $(
                    let $params = $params::retrieve(resources);
                )*

                call_inner(&mut self.f, $($params),*)
            }
        }
    }
}

macro_rules! impl_into_system {
    (
        $($params:ident),*
    ) => {
        impl<F, $($params: SystemParam),*> IntoSystem<($($params,)*)> for F
            where
                for<'a, 'b> &'a mut F:
                    FnMut( $($params),* ) +
                    FnMut( $(<$params as SystemParam>::Item<'b>),* )
        {
            type System = FunctionSystem<($($params,)*), Self>;

            fn into_system(self) -> Self::System {
                FunctionSystem {
                    f: self,
                    marker: Default::default(),
                }
            }
        }
    }
}

// ANCHOR: SystemParam
trait SystemParam {
    type Item<'new>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r>;
}
// ANCHOR_END: SystemParam

// ANCHOR: ResSystemParam
impl<'res, T: 'static> SystemParam for Res<'res, T> {
    type Item<'new> = Res<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r> {
        Res {
            value: resources.get(&TypeId::of::<T>()).unwrap().borrow(),
            _marker: PhantomData,
        }
    }
}
// ANCHOR_END: ResSystemParam

// ANCHOR: ResMutSystemParam
impl<'res, T: 'static> SystemParam for ResMut<'res, T> {
    type Item<'new> = ResMut<'new, T>;

    fn retrieve<'r>(resources: &'r HashMap<TypeId, RefCell<Box<dyn Any>>>) -> Self::Item<'r> {
        ResMut {
            value: resources.get(&TypeId::of::<T>()).unwrap().borrow_mut(),
            _marker: PhantomData,
        }
    }
}
// ANCHOR_END: ResMutSystemParam

// ANCHOR: Res
struct Res<'a, T: 'static> {
    value: Ref<'a, Box<dyn Any>>,
    _marker: PhantomData<&'a T>,
}
// ANCHOR_END: Res

// ANCHOR: ResDeref
impl<T: 'static> Deref for Res<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.value.downcast_ref().unwrap()
    }
}
// ANCHOR_END: ResDeref

// ANCHOR: ResMut
struct ResMut<'a, T: 'static> {
    value: RefMut<'a, Box<dyn Any>>,
    _marker: PhantomData<&'a mut T>,
}

impl<T: 'static> Deref for ResMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.value.downcast_ref().unwrap()
    }
}

impl<T: 'static> DerefMut for ResMut<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        self.value.downcast_mut().unwrap()
    }
}
// ANCHOR_END: ResMut

struct FunctionSystem<Input, F> {
    f: F,
    marker: PhantomData<fn() -> Input>,
}

// ANCHOR: System
trait System {
    fn run(&mut self, resources: &mut HashMap<TypeId, RefCell<Box<dyn Any>>>);
}
// ANCHOR_END: System

impl_system!();
impl_system!(T1);
impl_system!(T1, T2);
impl_system!(T1, T2, T3);
impl_system!(T1, T2, T3, T4);

trait IntoSystem<Input> {
    type System: System;

    fn into_system(self) -> Self::System;
}

impl_into_system!();
impl_into_system!(T1);
impl_into_system!(T1, T2);
impl_into_system!(T1, T2, T3);
impl_into_system!(T1, T2, T3, T4);

type StoredSystem = Box<dyn System>;

// ANCHOR: Scheduler
#[derive(Default)]
struct Scheduler {
    systems: Vec<StoredSystem>,
    resources: HashMap<TypeId, RefCell<Box<dyn Any>>>,
}
// ANCHOR_END: Scheduler

// ANCHOR: SchedulerImpl
impl Scheduler {
    pub fn run(&mut self) {
        for system in self.systems.iter_mut() {
            system.run(&mut self.resources);
        }
    }

    pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
        self.systems.push(Box::new(system.into_system()));
    }

    pub fn add_resource<R: 'static>(&mut self, res: R) {
        self.resources
            .insert(TypeId::of::<R>(), RefCell::new(Box::new(res)));
    }
}
// ANCHOR_END: SchedulerImpl
// ANCHOR_END: All
fn main() {
    let mut scheduler = Scheduler::default();
    scheduler.add_system(spooky);
    scheduler.add_resource(13i32);

    scheduler.run();
}

fn spooky(_foo: ResMut<i32>, _bar: ResMut<i32>) {
    println!("Haha lmao");
}

We of course still can't borrow the same resource mutably multiple times at once, and RefCell will prevent this by panicking if we ever try to construct an ill-formed system like this. Bevy will do something similar, but with a better error message; We will do it manually in the next section.