Merge "First pass at tuning 2024 subsystems"
diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock
index e70a4ca..6bf8948 100644
--- a/Cargo.Bazel.lock
+++ b/Cargo.Bazel.lock
@@ -1,5 +1,5 @@
 {
-  "checksum": "7305dfa1e83bfd0222912e22f8294905db9c35e036a34182aaaf7bafa13c28ac",
+  "checksum": "6c02572fd1f0170376a09b2b84af516c934a7918e2fa5f5842e6a3cbccb0cb31",
   "crates": {
     "addr2line 0.20.0": {
       "name": "addr2line",
@@ -233,6 +233,76 @@
       },
       "license": "MIT"
     },
+    "anstream 0.6.11": {
+      "name": "anstream",
+      "version": "0.6.11",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/anstream/0.6.11/download",
+          "sha256": "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "anstream",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "anstream",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "auto",
+            "default",
+            "wincon"
+          ],
+          "selects": {}
+        },
+        "deps": {
+          "common": [
+            {
+              "id": "anstyle 1.0.1",
+              "target": "anstyle"
+            },
+            {
+              "id": "anstyle-parse 0.2.3",
+              "target": "anstyle_parse"
+            },
+            {
+              "id": "anstyle-query 1.0.2",
+              "target": "anstyle_query"
+            },
+            {
+              "id": "colorchoice 1.0.0",
+              "target": "colorchoice"
+            },
+            {
+              "id": "utf8parse 0.2.1",
+              "target": "utf8parse"
+            }
+          ],
+          "selects": {
+            "cfg(windows)": [
+              {
+                "id": "anstyle-wincon 3.0.2",
+                "target": "anstyle_wincon"
+              }
+            ]
+          }
+        },
+        "edition": "2021",
+        "version": "0.6.11"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "anstyle 1.0.1": {
       "name": "anstyle",
       "version": "1.0.1",
@@ -270,6 +340,139 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "anstyle-parse 0.2.3": {
+      "name": "anstyle-parse",
+      "version": "0.2.3",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/anstyle-parse/0.2.3/download",
+          "sha256": "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "anstyle_parse",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "anstyle_parse",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "default",
+            "utf8"
+          ],
+          "selects": {}
+        },
+        "deps": {
+          "common": [
+            {
+              "id": "utf8parse 0.2.1",
+              "target": "utf8parse"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.2.3"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
+    "anstyle-query 1.0.2": {
+      "name": "anstyle-query",
+      "version": "1.0.2",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/anstyle-query/1.0.2/download",
+          "sha256": "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "anstyle_query",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "anstyle_query",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [],
+          "selects": {
+            "cfg(windows)": [
+              {
+                "id": "windows-sys 0.52.0",
+                "target": "windows_sys"
+              }
+            ]
+          }
+        },
+        "edition": "2021",
+        "version": "1.0.2"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
+    "anstyle-wincon 3.0.2": {
+      "name": "anstyle-wincon",
+      "version": "3.0.2",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/anstyle-wincon/3.0.2/download",
+          "sha256": "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "anstyle_wincon",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "anstyle_wincon",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "anstyle 1.0.1",
+              "target": "anstyle"
+            }
+          ],
+          "selects": {
+            "cfg(windows)": [
+              {
+                "id": "windows-sys 0.52.0",
+                "target": "windows_sys"
+              }
+            ]
+          }
+        },
+        "edition": "2021",
+        "version": "3.0.2"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "anyhow 1.0.71": {
       "name": "anyhow",
       "version": "1.0.71",
@@ -385,6 +588,43 @@
       },
       "license": "MIT"
     },
+    "arrayvec 0.7.4": {
+      "name": "arrayvec",
+      "version": "0.7.4",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/arrayvec/0.7.4/download",
+          "sha256": "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "arrayvec",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "arrayvec",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "default",
+            "std"
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.7.4"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "assert_cmd 2.0.12": {
       "name": "assert_cmd",
       "version": "2.0.12",
@@ -863,7 +1103,8 @@
           "common": [
             {
               "id": "clap 3.2.25",
-              "target": "clap"
+              "target": "clap",
+              "alias": "clap3"
             },
             {
               "id": "env_logger 0.9.3",
@@ -1981,6 +2222,188 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "clap 4.4.18": {
+      "name": "clap",
+      "version": "4.4.18",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/clap/4.4.18/download",
+          "sha256": "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "clap",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "clap",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "color",
+            "default",
+            "derive",
+            "error-context",
+            "help",
+            "std",
+            "string",
+            "suggestions",
+            "usage"
+          ],
+          "selects": {}
+        },
+        "deps": {
+          "common": [
+            {
+              "id": "clap_builder 4.4.18",
+              "target": "clap_builder"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "proc_macro_deps": {
+          "common": [
+            {
+              "id": "clap_derive 4.4.7",
+              "target": "clap_derive"
+            }
+          ],
+          "selects": {}
+        },
+        "version": "4.4.18"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
+    "clap_builder 4.4.18": {
+      "name": "clap_builder",
+      "version": "4.4.18",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/clap_builder/4.4.18/download",
+          "sha256": "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "clap_builder",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "clap_builder",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "color",
+            "error-context",
+            "help",
+            "std",
+            "string",
+            "suggestions",
+            "usage"
+          ],
+          "selects": {}
+        },
+        "deps": {
+          "common": [
+            {
+              "id": "anstream 0.6.11",
+              "target": "anstream"
+            },
+            {
+              "id": "anstyle 1.0.1",
+              "target": "anstyle"
+            },
+            {
+              "id": "clap_lex 0.6.0",
+              "target": "clap_lex"
+            },
+            {
+              "id": "strsim 0.10.0",
+              "target": "strsim"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "4.4.18"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
+    "clap_derive 4.4.7": {
+      "name": "clap_derive",
+      "version": "4.4.7",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/clap_derive/4.4.7/download",
+          "sha256": "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+        }
+      },
+      "targets": [
+        {
+          "ProcMacro": {
+            "crate_name": "clap_derive",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "clap_derive",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "default"
+          ],
+          "selects": {}
+        },
+        "deps": {
+          "common": [
+            {
+              "id": "heck 0.4.1",
+              "target": "heck"
+            },
+            {
+              "id": "proc-macro2 1.0.63",
+              "target": "proc_macro2"
+            },
+            {
+              "id": "quote 1.0.29",
+              "target": "quote"
+            },
+            {
+              "id": "syn 2.0.28",
+              "target": "syn"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "4.4.7"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "clap_lex 0.2.4": {
       "name": "clap_lex",
       "version": "0.2.4",
@@ -2009,7 +2432,7 @@
         "deps": {
           "common": [
             {
-              "id": "os_str_bytes 6.5.1",
+              "id": "os_str_bytes 6.6.1",
               "target": "os_str_bytes"
             }
           ],
@@ -2020,6 +2443,36 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "clap_lex 0.6.0": {
+      "name": "clap_lex",
+      "version": "0.6.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/clap_lex/0.6.0/download",
+          "sha256": "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "clap_lex",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "clap_lex",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "edition": "2021",
+        "version": "0.6.0"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "codespan-reporting 0.11.1": {
       "name": "codespan-reporting",
       "version": "0.11.1",
@@ -2063,6 +2516,36 @@
       },
       "license": "Apache-2.0"
     },
+    "colorchoice 1.0.0": {
+      "name": "colorchoice",
+      "version": "1.0.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/colorchoice/1.0.0/download",
+          "sha256": "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "colorchoice",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "colorchoice",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "edition": "2021",
+        "version": "1.0.0"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "compile_with_bazel 0.0.0": {
       "name": "compile_with_bazel",
       "version": "0.0.0",
@@ -2098,6 +2581,10 @@
               "target": "bitflags"
             },
             {
+              "id": "clap 4.4.18",
+              "target": "clap"
+            },
+            {
               "id": "cxx 1.0.97",
               "target": "cxx"
             },
@@ -2805,6 +3292,10 @@
         "deps": {
           "common": [
             {
+              "id": "arrayvec 0.7.4",
+              "target": "arrayvec"
+            },
+            {
               "id": "bitflags 1.3.2",
               "target": "bitflags"
             },
@@ -3554,6 +4045,42 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "heck 0.4.1": {
+      "name": "heck",
+      "version": "0.4.1",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/heck/0.4.1/download",
+          "sha256": "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "heck",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "heck",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "default"
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.4.1"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "hermit-abi 0.1.19": {
       "name": "hermit-abi",
       "version": "0.1.19",
@@ -5059,13 +5586,13 @@
       },
       "license": "MIT OR Apache-2.0"
     },
-    "os_str_bytes 6.5.1": {
+    "os_str_bytes 6.6.1": {
       "name": "os_str_bytes",
-      "version": "6.5.1",
+      "version": "6.6.1",
       "repository": {
         "Http": {
-          "url": "https://crates.io/api/v1/crates/os_str_bytes/6.5.1/download",
-          "sha256": "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
+          "url": "https://crates.io/api/v1/crates/os_str_bytes/6.6.1/download",
+          "sha256": "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
         }
       },
       "targets": [
@@ -5091,7 +5618,7 @@
           "selects": {}
         },
         "edition": "2021",
-        "version": "6.5.1"
+        "version": "6.6.1"
       },
       "license": "MIT OR Apache-2.0"
     },
@@ -7928,6 +8455,42 @@
       },
       "license": "MIT/Apache-2.0"
     },
+    "utf8parse 0.2.1": {
+      "name": "utf8parse",
+      "version": "0.2.1",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/utf8parse/0.2.1/download",
+          "sha256": "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "utf8parse",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "utf8parse",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "crate_features": {
+          "common": [
+            "default"
+          ],
+          "selects": {}
+        },
+        "edition": "2018",
+        "version": "0.2.1"
+      },
+      "license": "Apache-2.0 OR MIT"
+    },
     "uuid 1.4.0": {
       "name": "uuid",
       "version": "1.4.0",
@@ -8437,6 +9000,45 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows-sys 0.52.0": {
+      "name": "windows-sys",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows-sys/0.52.0/download",
+          "sha256": "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_sys",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_sys",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows-targets 0.52.0",
+              "target": "windows_targets"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows-targets 0.48.1": {
       "name": "windows-targets",
       "version": "0.48.1",
@@ -8514,6 +9116,83 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows-targets 0.52.0": {
+      "name": "windows-targets",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows-targets/0.52.0/download",
+          "sha256": "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_targets",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_targets",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [],
+          "selects": {
+            "aarch64-pc-windows-gnullvm": [
+              {
+                "id": "windows_aarch64_gnullvm 0.52.0",
+                "target": "windows_aarch64_gnullvm"
+              }
+            ],
+            "cfg(all(target_arch = \"aarch64\", target_env = \"msvc\", not(windows_raw_dylib)))": [
+              {
+                "id": "windows_aarch64_msvc 0.52.0",
+                "target": "windows_aarch64_msvc"
+              }
+            ],
+            "cfg(all(target_arch = \"x86\", target_env = \"gnu\", not(windows_raw_dylib)))": [
+              {
+                "id": "windows_i686_gnu 0.52.0",
+                "target": "windows_i686_gnu"
+              }
+            ],
+            "cfg(all(target_arch = \"x86\", target_env = \"msvc\", not(windows_raw_dylib)))": [
+              {
+                "id": "windows_i686_msvc 0.52.0",
+                "target": "windows_i686_msvc"
+              }
+            ],
+            "cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))": [
+              {
+                "id": "windows_x86_64_gnu 0.52.0",
+                "target": "windows_x86_64_gnu"
+              }
+            ],
+            "cfg(all(target_arch = \"x86_64\", target_env = \"msvc\", not(windows_raw_dylib)))": [
+              {
+                "id": "windows_x86_64_msvc 0.52.0",
+                "target": "windows_x86_64_msvc"
+              }
+            ],
+            "x86_64-pc-windows-gnullvm": [
+              {
+                "id": "windows_x86_64_gnullvm 0.52.0",
+                "target": "windows_x86_64_gnullvm"
+              }
+            ]
+          }
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_aarch64_gnullvm 0.48.0": {
       "name": "windows_aarch64_gnullvm",
       "version": "0.48.0",
@@ -8567,6 +9246,59 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows_aarch64_gnullvm 0.52.0": {
+      "name": "windows_aarch64_gnullvm",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_aarch64_gnullvm/0.52.0/download",
+          "sha256": "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_aarch64_gnullvm",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_aarch64_gnullvm",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_aarch64_gnullvm 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_aarch64_msvc 0.48.0": {
       "name": "windows_aarch64_msvc",
       "version": "0.48.0",
@@ -8620,6 +9352,59 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows_aarch64_msvc 0.52.0": {
+      "name": "windows_aarch64_msvc",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_aarch64_msvc/0.52.0/download",
+          "sha256": "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_aarch64_msvc",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_aarch64_msvc",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_aarch64_msvc 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_i686_gnu 0.48.0": {
       "name": "windows_i686_gnu",
       "version": "0.48.0",
@@ -8673,6 +9458,59 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows_i686_gnu 0.52.0": {
+      "name": "windows_i686_gnu",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_i686_gnu/0.52.0/download",
+          "sha256": "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_i686_gnu",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_i686_gnu",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_i686_gnu 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_i686_msvc 0.48.0": {
       "name": "windows_i686_msvc",
       "version": "0.48.0",
@@ -8726,6 +9564,59 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows_i686_msvc 0.52.0": {
+      "name": "windows_i686_msvc",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_i686_msvc/0.52.0/download",
+          "sha256": "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_i686_msvc",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_i686_msvc",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_i686_msvc 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_x86_64_gnu 0.48.0": {
       "name": "windows_x86_64_gnu",
       "version": "0.48.0",
@@ -8779,6 +9670,59 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows_x86_64_gnu 0.52.0": {
+      "name": "windows_x86_64_gnu",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_x86_64_gnu/0.52.0/download",
+          "sha256": "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_x86_64_gnu",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_x86_64_gnu",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_x86_64_gnu 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_x86_64_gnullvm 0.48.0": {
       "name": "windows_x86_64_gnullvm",
       "version": "0.48.0",
@@ -8832,6 +9776,59 @@
       },
       "license": "MIT OR Apache-2.0"
     },
+    "windows_x86_64_gnullvm 0.52.0": {
+      "name": "windows_x86_64_gnullvm",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_x86_64_gnullvm/0.52.0/download",
+          "sha256": "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_x86_64_gnullvm",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_x86_64_gnullvm",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_x86_64_gnullvm 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
+    },
     "windows_x86_64_msvc 0.48.0": {
       "name": "windows_x86_64_msvc",
       "version": "0.48.0",
@@ -8884,6 +9881,59 @@
         ]
       },
       "license": "MIT OR Apache-2.0"
+    },
+    "windows_x86_64_msvc 0.52.0": {
+      "name": "windows_x86_64_msvc",
+      "version": "0.52.0",
+      "repository": {
+        "Http": {
+          "url": "https://crates.io/api/v1/crates/windows_x86_64_msvc/0.52.0/download",
+          "sha256": "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "windows_x86_64_msvc",
+            "crate_root": "src/lib.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs",
+            "srcs": [
+              "**/*.rs"
+            ]
+          }
+        }
+      ],
+      "library_target_name": "windows_x86_64_msvc",
+      "common_attrs": {
+        "compile_data_glob": [
+          "**"
+        ],
+        "deps": {
+          "common": [
+            {
+              "id": "windows_x86_64_msvc 0.52.0",
+              "target": "build_script_build"
+            }
+          ],
+          "selects": {}
+        },
+        "edition": "2021",
+        "version": "0.52.0"
+      },
+      "build_script_attrs": {
+        "data_glob": [
+          "**"
+        ]
+      },
+      "license": "MIT OR Apache-2.0"
     }
   },
   "binary_crates": [],
diff --git a/Cargo.lock b/Cargo.lock
index 5fcc5eb..3829492 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -47,12 +47,54 @@
 ]
 
 [[package]]
+name = "anstream"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
 name = "anstyle"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
 
 [[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
 name = "anyhow"
 version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -72,6 +114,12 @@
 ]
 
 [[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
 name = "assert_cmd"
 version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -385,7 +433,7 @@
 dependencies = [
  "atty",
  "bitflags 1.3.2",
- "clap_lex",
+ "clap_lex 0.2.4",
  "indexmap",
  "once_cell",
  "strsim 0.10.0",
@@ -394,6 +442,40 @@
 ]
 
 [[package]]
+name = "clap"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex 0.6.0",
+ "strsim 0.10.0",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.28",
+]
+
+[[package]]
 name = "clap_lex"
 version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -403,6 +485,12 @@
 ]
 
 [[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
 name = "codespan-reporting"
 version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -413,12 +501,19 @@
 ]
 
 [[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
 name = "compile_with_bazel"
 version = "0.0.0"
 dependencies = [
  "anyhow",
  "bindgen",
  "bitflags 1.3.2",
+ "clap 4.4.18",
  "cxx",
  "cxxbridge-macro",
  "futures",
@@ -524,7 +619,7 @@
 dependencies = [
  "errno-dragonfly",
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -550,6 +645,7 @@
 name = "flatbuffers"
 version = "22.10.26"
 dependencies = [
+ "arrayvec",
  "bitflags 1.3.2",
  "rustc_version",
  "serde",
@@ -677,6 +773,12 @@
 ]
 
 [[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
 name = "hermit-abi"
 version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -731,7 +833,7 @@
 dependencies = [
  "hermit-abi 0.3.2",
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -742,7 +844,7 @@
 dependencies = [
  "hermit-abi 0.3.2",
  "rustix 0.38.7",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -939,9 +1041,9 @@
 
 [[package]]
 name = "os_str_bytes"
-version = "6.5.1"
+version = "6.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
 
 [[package]]
 name = "owo-colors"
@@ -1129,7 +1231,7 @@
  "io-lifetimes",
  "libc",
  "linux-raw-sys 0.3.8",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1142,7 +1244,7 @@
  "errno",
  "libc",
  "linux-raw-sys 0.4.5",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1300,7 +1402,7 @@
  "fastrand",
  "redox_syscall",
  "rustix 0.37.23",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1432,6 +1534,12 @@
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
 name = "uuid"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1521,7 +1629,16 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -1530,13 +1647,28 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
 ]
 
 [[package]]
@@ -1546,37 +1678,79 @@
 checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
 
 [[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
 
 [[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
 name = "windows_i686_gnu"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
 
 [[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
 name = "windows_i686_msvc"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
 
 [[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
 
 [[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
 
 [[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/Cargo.toml b/Cargo.toml
index 8d07945..993491a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@
 ]
 
 [dependencies]
+clap = { version = "4.4", features = ["derive", "string"] }
 cxx = "1.0"
 cxxbridge-macro = "1.0"
 uuid = "1.0"
diff --git a/aos/BUILD b/aos/BUILD
index 7c0ad3d..67e5b29 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -1,9 +1,9 @@
-load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library", "flatbuffer_py_library", "flatbuffer_rust_library")
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library", "flatbuffer_py_library")
 load("//aos/flatbuffers:generate.bzl", "static_flatbuffer")
 load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
-load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
 load("//aos:flatbuffers.bzl", "cc_static_flatbuffer")
 load("//tools/build_rules:autocxx.bzl", "autocxx_library")
+load("//tools/rust:defs.bzl", "flatbuffer_rust_library", "rust_library")
 
 exports_files(["aos_dump_autocomplete.sh"])
 
@@ -184,19 +184,36 @@
     ],
 )
 
+cc_library(
+    name = "init_for_rust",
+    srcs = [
+        "init_for_rust.cc",
+    ],
+    hdrs = [
+        "init_for_rust.h",
+    ],
+    deps = [
+        ":for_rust",
+        ":init",
+        "//aos/logging",
+        "@com_github_gflags_gflags//:gflags",
+        "@crate_index//:cxx_cc",
+    ],
+)
+
 autocxx_library(
     name = "init_rs",
     srcs = ["init.rs"],
     crate_name = "aos_init",
     libs = [
-        ":init",
+        ":init_for_rust",
     ],
     override_cc_toolchain = "@llvm_toolchain//:cc-clang-x86_64-linux",
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     visibility = ["//visibility:public"],
+    deps = [
+        "@crate_index//:clap",
+        "@crate_index//:env_logger",
+    ],
 )
 
 autocxx_library(
@@ -204,17 +221,15 @@
     testonly = True,
     srcs = ["test_init.rs"],
     crate_name = "aos_test_init",
+    gen_docs = False,
     libs = [
         "//aos/testing:tmpdir",
     ],
     override_cc_toolchain = "@llvm_toolchain//:cc-clang-x86_64-linux",
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     visibility = ["//visibility:public"],
     deps = [
         ":init_rs",
+        "@crate_index//:env_logger",
     ],
 )
 
@@ -275,10 +290,6 @@
     name = "configuration_rust_fbs",
     srcs = ["configuration.fbs"],
     crate_name = "aos_configuration_fbs",
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     visibility = ["//visibility:public"],
 )
 
@@ -332,10 +343,9 @@
         ":configuration_fbs",
     ],
     override_cc_toolchain = "@llvm_toolchain//:cc-clang-x86_64-linux",
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
+    test_data = [
+        "//aos/testdata:test_configs",
+    ],
     visibility = ["//visibility:public"],
     deps = [
         ":configuration_rust_fbs",
@@ -344,17 +354,6 @@
     ],
 )
 
-rust_test(
-    name = "configuration_rs_test",
-    crate = ":configuration_rs",
-    data = [
-        "//aos/testdata:test_configs",
-    ],
-    # TODO: Make Rust play happy with pic vs nopic. Details at:
-    # https://github.com/bazelbuild/rules_rust/issues/118
-    rustc_flags = ["-Crelocation-model=static"],
-)
-
 flatbuffer_ts_library(
     name = "json_to_flatbuffer_fbs_ts",
     srcs = ["json_to_flatbuffer.fbs"],
@@ -374,10 +373,6 @@
     name = "json_to_flatbuffer_rust_fbs",
     srcs = ["json_to_flatbuffer.fbs"],
     crate_name = "aos_json_to_flatbuffer_fbs",
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     visibility = ["//aos:__subpackages__"],
 )
 
@@ -518,24 +513,15 @@
     name = "flatbuffers_rs",
     srcs = ["flatbuffers.rs"],
     crate_name = "aos_flatbuffers",
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
+    test_deps = [
+        ":json_to_flatbuffer_rust_fbs",
+    ],
     visibility = ["//visibility:public"],
     deps = [
         "@com_github_google_flatbuffers//rust",
     ],
 )
 
-rust_test(
-    name = "flatbuffers_rs_test",
-    crate = ":flatbuffers_rs",
-    deps = [
-        ":json_to_flatbuffer_rust_fbs",
-    ],
-)
-
 cc_test(
     name = "configuration_test",
     srcs = [
@@ -766,10 +752,6 @@
     rs_deps = [
         "@crate_index//:uuid",
     ],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     visibility = ["//visibility:public"],
 )
 
@@ -826,3 +808,19 @@
         "//aos/testing:tmpdir",
     ],
 )
+
+rust_library(
+    name = "aos_rs",
+    srcs = ["aos.rs"],
+    crate_name = "aos",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":configuration_rs",
+        ":flatbuffers_rs",
+        ":init_rs",
+        ":uuid_rs",
+        "//aos/events:event_loop_runtime",
+        "//aos/events:shm_event_loop_rs",
+        "//aos/events:simulated_event_loop_rs",
+    ],
+)
diff --git a/aos/aos.rs b/aos/aos.rs
new file mode 100644
index 0000000..3a13c4d
--- /dev/null
+++ b/aos/aos.rs
@@ -0,0 +1,14 @@
+//! All of AOS into a single, easy to use library.
+
+pub use aos_configuration as configuration;
+pub use aos_init as init;
+pub use aos_uuid as uuid;
+
+pub use aos_flatbuffers as flatbuffers;
+
+/// The essentials for working with the AOS event loop.
+pub mod events {
+    pub use aos_events_event_loop_runtime as event_loop_runtime;
+    pub use aos_events_shm_event_loop as shm_event_loop;
+    pub use aos_events_simulated_event_loop as simulated_event_loop;
+}
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 9910fc6..e82fdde 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -1,10 +1,9 @@
-load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_rust_library")
 load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
 load("//aos/flatbuffers:generate.bzl", "static_flatbuffer")
 load("//aos:flatbuffers.bzl", "cc_static_flatbuffer")
 load("//aos:config.bzl", "aos_config")
 load("//tools/build_rules:autocxx.bzl", "autocxx_library")
-load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_doc", "rust_doc_test", "rust_test")
+load("//tools/rust:defs.bzl", "flatbuffer_rust_library", "rust_binary", "rust_library", "rust_test")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -46,19 +45,11 @@
 flatbuffer_rust_library(
     name = "ping_rust_fbs",
     srcs = ["ping.fbs"],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
 )
 
 flatbuffer_rust_library(
     name = "pong_rust_fbs",
     srcs = ["pong.fbs"],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
 )
 
 static_flatbuffer(
@@ -155,6 +146,10 @@
     name = "event_loop_runtime",
     srcs = ["event_loop_runtime.rs"],
     crate_name = "aos_events_event_loop_runtime",
+    doctest_deps = [
+        ":pong_rust_fbs",
+    ],
+    gen_tests = False,
     libs = [
         ":event_loop_runtime_cc",
     ],
@@ -165,10 +160,6 @@
         "@crate_index//:futures",
         "@crate_index//:thiserror",
     ],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     visibility = ["//visibility:public"],
     deps = [
         "//aos:configuration_rs",
@@ -176,37 +167,19 @@
     ],
 )
 
-rust_doc(
-    name = "event_loop_runtime_doc",
-    crate = ":event_loop_runtime",
-)
-
-rust_doc_test(
-    name = "event_loop_runtime_doc_test",
-    crate = ":event_loop_runtime",
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-    deps = [
-        ":pong_rust_fbs",
-    ],
-)
-
 autocxx_library(
     name = "event_loop_runtime_test_lib_rs",
     testonly = True,
     srcs = ["event_loop_runtime_test_lib.rs"],
+    gen_docs = False,
     libs = [
         ":event_loop",
     ],
-    override_cc_toolchain = "@llvm_toolchain//:cc-clang-x86_64-linux",
     rs_deps = [
         ":event_loop_runtime",
         ":ping_rust_fbs",
         ":pong_rust_fbs",
     ],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
 )
 
 cc_test(
@@ -259,6 +232,28 @@
     ],
 )
 
+rust_test(
+    name = "pingpong_test_rs",
+    srcs = [
+        "pingpong_test.rs",
+    ],
+    data = [":pingpong_config"],
+    # TODO(adam.snaider): Remove later. For now we need this because when
+    # a rust test crashes inside of C++, the output gets captured which makes
+    # it pretty much impossible to figure out what happened.
+    env = {"RUST_TEST_NOCAPTURE": "1"},
+    deps = [
+        ":ping_lib_rs",
+        ":ping_rust_fbs",
+        ":pong_lib_rs",
+        ":pong_rust_fbs",
+        "//aos/testing:aos_rs",
+        "@com_github_google_flatbuffers//rust",
+        "@crate_index//:futures",
+        "@rules_rust//tools/runfiles",
+    ],
+)
+
 rust_binary(
     name = "ping_rs",
     srcs = [
@@ -267,22 +262,27 @@
     data = [
         ":pingpong_config",
     ],
-    rustc_flags = ["-Crelocation-model=static"],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     deps = [
-        ":event_loop_runtime",
+        ":ping_lib_rs",
+        ":shm_event_loop_rs",
+        "//aos:aos_rs",
+        "@crate_index//:clap",
+    ],
+)
+
+rust_library(
+    name = "ping_lib_rs",
+    srcs = [
+        "ping_lib.rs",
+    ],
+    crate_name = "ping_lib",
+    deps = [
         ":ping_rust_fbs",
         ":pong_rust_fbs",
-        ":shm_event_loop_rs",
-        "//aos:configuration_rs",
-        "//aos:configuration_rust_fbs",
-        "//aos:flatbuffers_rs",
-        "//aos:init_rs",
+        "//aos:aos_rs",
         "@com_github_google_flatbuffers//rust",
         "@crate_index//:futures",
+        "@crate_index//:log",
     ],
 )
 
@@ -294,22 +294,27 @@
     data = [
         ":pingpong_config",
     ],
-    rustc_flags = ["-Crelocation-model=static"],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
     deps = [
-        ":event_loop_runtime",
+        ":pong_lib_rs",
+        ":shm_event_loop_rs",
+        "//aos:aos_rs",
+        "@crate_index//:clap",
+    ],
+)
+
+rust_library(
+    name = "pong_lib_rs",
+    srcs = [
+        "pong_lib.rs",
+    ],
+    crate_name = "pong_lib",
+    deps = [
         ":ping_rust_fbs",
         ":pong_rust_fbs",
-        ":shm_event_loop_rs",
-        "//aos:configuration_rs",
-        "//aos:configuration_rust_fbs",
-        "//aos:flatbuffers_rs",
-        "//aos:init_rs",
+        "//aos:aos_rs",
         "@com_github_google_flatbuffers//rust",
         "@crate_index//:futures",
+        "@crate_index//:log",
     ],
 )
 
@@ -599,6 +604,15 @@
         "//aos:flatbuffers_rs",
         "@crate_index//:futures",
     ],
+    test_data = [
+        ":multinode_pingpong_test_combined_config",
+    ],
+    test_deps = [
+        ":ping_rust_fbs",
+        "//aos:test_init_rs",
+        "@crate_index//:futures",
+        "@rules_rust//tools/runfiles",
+    ],
     visibility = ["//visibility:public"],
     deps = [
         ":event_loop_runtime",
@@ -607,27 +621,14 @@
     ],
 )
 
-rust_test(
-    name = "simulated_event_loop_rs_test",
-    crate = ":simulated_event_loop_rs",
-    data = [
-        ":multinode_pingpong_test_combined_config",
-    ],
-    # TODO: Make Rust play happy with pic vs nopic. Details at:
-    # https://github.com/bazelbuild/rules_rust/issues/118
-    rustc_flags = ["-Crelocation-model=static"],
-    deps = [
-        ":ping_rust_fbs",
-        "//aos:test_init_rs",
-        "@crate_index//:futures",
-        "@rules_rust//tools/runfiles",
-    ],
-)
-
 autocxx_library(
     name = "shm_event_loop_rs",
     srcs = ["shm_event_loop.rs"],
     crate_name = "aos_events_shm_event_loop",
+    doctest_deps = [
+        ":ping_rust_fbs",
+        ":pong_rust_fbs",
+    ],
     libs = [
         ":shm_event_loop",
         ":shm_event_loop_for_rust",
@@ -638,49 +639,17 @@
         "//aos:configuration_rust_fbs",
         "//aos:flatbuffers_rs",
     ],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
-    visibility = ["//visibility:public"],
-    deps = [
-        ":event_loop_runtime",
-        "//aos:configuration_rs",
-    ],
-)
-
-rust_doc(
-    name = "shm_event_loop_rs_doc",
-    crate = ":shm_event_loop_rs",
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-)
-
-rust_test(
-    name = "shm_event_loop_rs_test",
-    crate = ":shm_event_loop_rs",
-    data = [":pingpong_config"],
-    # TODO: Make Rust play happy with pic vs nopic. Details at:
-    # https://github.com/bazelbuild/rules_rust/issues/118
-    rustc_flags = ["-Crelocation-model=static"],
-    target_compatible_with = select({
-        "//conditions:default": ["//tools/platforms/rust:has_support"],
-        "//tools:has_msan": ["@platforms//:incompatible"],
-    }),
-    deps = [
+    test_data = [":pingpong_config"],
+    test_deps = [
         ":ping_rust_fbs",
         "//aos:test_init_rs",
         "@crate_index//:futures",
         "@rules_rust//tools/runfiles",
     ],
-)
-
-rust_doc_test(
-    name = "shm_event_loop_rs_doc_test",
-    crate = ":shm_event_loop_rs",
-    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
     deps = [
-        ":ping_rust_fbs",
-        ":pong_rust_fbs",
+        ":event_loop_runtime",
+        "//aos:configuration_rs",
     ],
 )
 
diff --git a/aos/events/event_loop_runtime.h b/aos/events/event_loop_runtime.h
index 7c23456..0505852 100644
--- a/aos/events/event_loop_runtime.h
+++ b/aos/events/event_loop_runtime.h
@@ -177,7 +177,16 @@
 
 class EventLoopRuntime {
  public:
-  EventLoopRuntime(EventLoop *event_loop) : event_loop_(event_loop) {}
+  EventLoopRuntime(const EventLoop *event_loop)
+      // SAFETY: A &EventLoop in Rust becomes a const EventLoop*. While
+      // that's generally a reasonable convention, they are semantically
+      // different. In Rust, a &mut EventLoop is very restrictive as it enforces
+      // uniqueness of the reference. Additionally, a &EventLoop doesn't convey
+      // const-ness in the C++ sense. So to make the FFI boundary more
+      // ergonomic, we allow a &EventLoop passed from rust to be translated into
+      // an EventLoop* in C++. This is safe so long as EventLoop is !Sync and no
+      // &mut EventLoop references are constructed in Rust.
+      : event_loop_(const_cast<EventLoop *>(event_loop)) {}
   ~EventLoopRuntime() {
     // Do this first, because it may hold child objects.
     task_.reset();
diff --git a/aos/events/event_loop_runtime.rs b/aos/events/event_loop_runtime.rs
index 1f70bf6..18d743e 100644
--- a/aos/events/event_loop_runtime.rs
+++ b/aos/events/event_loop_runtime.rs
@@ -22,7 +22,7 @@
 //!    future".
 //! 3. Multiple applications are better suited to multiple `EventLoopRuntime`s, on separate
 //!    `aos::EventLoop`s. Otherwise they can't send messages to each other, among other
-//!    restrictions. https://github.com/frc971/971-Robot-Code/issues/12 covers creating an adapter
+//!    restrictions. <https://github.com/frc971/971-Robot-Code/issues/12> covers creating an adapter
 //!    that provides multiple `EventLoop`s on top of a single underlying implementation.
 //!
 //! ## Design
@@ -31,7 +31,7 @@
 //! considerations in arriving at this design include:
 //!   * `EventLoop` implementations alias the objects they're returning from C++, which means
 //!     creating Rust unique references to them is unsound. See
-//!     https://github.com/google/autocxx/issues/1146 for details.
+//!     <https://github.com/google/autocxx/issues/1146> for details.
 //!   * For various reasons autocxx can't directly wrap APIs using types ergonomic for C++. This and
 //!     the previous point mean we wrap all of the C++ objects specifically for this class.
 //!   * Rust's lifetimes are only flexible enough to track everything with a single big lifetime.
@@ -40,14 +40,13 @@
 //!   * We can't use [`futures::stream::Stream`] and all of its nice [`futures::stream::StreamExt`]
 //!     helpers for watchers because we need lifetime-generic `Item` types. Effectively we're making
 //!     a lending stream. This is very close to lending iterators, which is one of the motivating
-//!     examples for generic associated types (https://github.com/rust-lang/rust/issues/44265).
+//!     examples for generic associated types (<https://github.com/rust-lang/rust/issues/44265>).
 
 use std::{
     fmt,
     future::Future,
     marker::PhantomData,
-    mem::ManuallyDrop,
-    ops::Add,
+    ops::{Add, Deref, DerefMut},
     panic::{catch_unwind, AssertUnwindSafe},
     pin::Pin,
     slice,
@@ -60,7 +59,9 @@
     WithinBox,
 };
 use cxx::UniquePtr;
-use flatbuffers::{root_unchecked, Follow, FollowWith, FullyQualifiedName};
+use flatbuffers::{
+    root_unchecked, Allocator, FlatBufferBuilder, Follow, FollowWith, FullyQualifiedName,
+};
 use futures::{future::pending, future::FusedFuture, never::Never};
 use thiserror::Error;
 use uuid::Uuid;
@@ -69,6 +70,7 @@
 use aos_configuration::{ChannelLookupError, ConfigurationExt};
 
 pub use aos_uuid::UUID;
+pub use ffi::aos::EventLoop as CppEventLoop;
 pub use ffi::aos::EventLoopRuntime as CppEventLoopRuntime;
 pub use ffi::aos::ExitHandle as CppExitHandle;
 
@@ -95,8 +97,6 @@
 extern_cpp_type!("aos::UUID", crate::UUID)
 );
 
-pub type EventLoop = ffi::aos::EventLoop;
-
 /// A marker type which is invariant with respect to the given lifetime.
 ///
 /// When interacting with functions that take and return things with a given lifetime, the lifetime
@@ -161,35 +161,29 @@
 ///
 /// # Safety
 ///
-/// Objects implementing this trait *must* have mostly-exclusive (except for running it) ownership
-/// of the `aos::EventLoop` *for its entire lifetime*, which *must* be dropped when this object is.
-/// See [`EventLoopRuntime.new`]'s safety requirements for why this can be important and details of
-/// mostly-exclusive. In other words, nothing else may mutate it in any way except processing events
-/// (including dropping, because this object has to be the one to drop it).
+/// Objects implementing this trait must guarantee that the underlying event loop (as returned
+/// from [`EventLoopHolder::as_raw`]), must be valid for as long as this object is. One way to do
+/// this may be by managing ownership of the event loop with Rust's ownership semantics. However,
+/// this is not strictly necessary.
 ///
-/// This also implies semantics similar to `Pin<&mut ffi::aos::EventLoop>` for the underlying object.
-/// Implementations of this trait must have exclusive ownership of it, and the underlying object
-/// must not be moved.
+/// This also implies semantics similar to `Pin<&mut CppEventLoop>` for the underlying object.
+/// Implementations of this trait must guarantee that the underlying object must not be moved while
+/// this object exists.
 pub unsafe trait EventLoopHolder {
-    /// Converts this holder into a raw C++ pointer. This may be fed through other Rust and C++
-    /// code, and eventually passed back to [`from_raw`].
-    fn into_raw(self) -> *mut ffi::aos::EventLoop;
-
-    /// Converts a raw C++ pointer back to a holder object.
+    /// Returns the raw C++ pointer of the underlying event loop.
     ///
-    /// # Safety
-    ///
-    /// `raw` must be the result of [`into_raw`] on an instance of this same type. These raw
-    /// pointers *are not* interchangeable between implementations of this trait.
-    unsafe fn from_raw(raw: *mut ffi::aos::EventLoop) -> Self;
+    /// Caller can only assume this pointer is valid while `self` is still alive.
+    fn as_raw(&self) -> *const CppEventLoop;
 }
 
 /// Owns an [`EventLoopRuntime`] and its underlying `aos::EventLoop`, with safe management of the
 /// associated Rust lifetimes.
-pub struct EventLoopRuntimeHolder<T: EventLoopHolder>(
-    ManuallyDrop<Pin<Box<CppEventLoopRuntime>>>,
-    PhantomData<T>,
-);
+pub struct EventLoopRuntimeHolder<T: EventLoopHolder> {
+    // NOTE: `runtime` must get dropped first, so we declare it before the event_loop:
+    // https://doc.rust-lang.org/reference/destructors.html
+    _runtime: Pin<Box<CppEventLoopRuntime>>,
+    _event_loop: T,
+}
 
 impl<T: EventLoopHolder> EventLoopRuntimeHolder<T> {
     /// Creates a new [`EventLoopRuntime`] and runs an initialization function on it. This is a
@@ -235,59 +229,41 @@
     /// ```
     pub fn new<F>(event_loop: T, fun: F) -> Self
     where
-        F: for<'event_loop> FnOnce(&mut EventLoopRuntime<'event_loop>),
+        F: for<'event_loop> FnOnce(EventLoopRuntime<'event_loop>),
     {
-        // SAFETY: The EventLoopRuntime never escapes this function, which means the only code that
-        // observes its lifetime is `fun`. `fun` must be generic across any value of its
-        // `'event_loop` lifetime parameter, which means we can choose any lifetime here, which
-        // satisfies the safety requirements.
-        //
-        // This is a similar pattern as `std::thread::scope`, `ghost-cell`, etc. Note that unlike
-        // `std::thread::scope`, our inner functions (the async ones) are definitely not allowed to
-        // capture things from the calling scope of this function, so there's no `'env` equivalent.
-        // `ghost-cell` ends up looking very similar despite doing different things with the
-        // pattern, while `std::thread::scope` has a lot of additional complexity to achieve a
-        // similar result.
-        //
-        // `EventLoopHolder`s safety requirements prevent anybody else from touching the underlying
-        // `aos::EventLoop`.
-        let mut runtime = unsafe { EventLoopRuntime::new(event_loop.into_raw()) };
-        fun(&mut runtime);
-        Self(ManuallyDrop::new(runtime.into_cpp()), PhantomData)
+        // SAFETY: The event loop pointer produced by as_raw must be valid and it will get dropped
+        // first (see https://doc.rust-lang.org/reference/destructors.html)
+        let runtime = unsafe { CppEventLoopRuntime::new(event_loop.as_raw()).within_box() };
+        EventLoopRuntime::with(&runtime, fun);
+        Self {
+            _runtime: runtime,
+            _event_loop: event_loop,
+        }
     }
 }
 
-impl<T: EventLoopHolder> Drop for EventLoopRuntimeHolder<T> {
-    fn drop(&mut self) {
-        let event_loop = self.0.event_loop();
-        // SAFETY: We're not going to touch this field again. The underlying EventLoop will not be
-        // run again because we're going to drop it next.
-        unsafe { ManuallyDrop::drop(&mut self.0) };
-        // SAFETY: We took this from `into_raw`, and we just dropped the runtime which may contain
-        // Rust references to it.
-        unsafe { drop(T::from_raw(event_loop)) };
-    }
-}
-
+/// Manages the Rust interface to a *single* `aos::EventLoop`.
+///
+/// This is intended to be used by a single application.
+#[derive(Copy, Clone)]
 pub struct EventLoopRuntime<'event_loop>(
-    Pin<Box<ffi::aos::EventLoopRuntime>>,
+    &'event_loop CppEventLoopRuntime,
     // See documentation of [`new`] for details.
     InvariantLifetime<'event_loop>,
 );
 
-/// Manages the Rust interface to a *single* `aos::EventLoop`. This is intended to be used by a
-/// single application.
 impl<'event_loop> EventLoopRuntime<'event_loop> {
-    /// Creates a new runtime. This must be the only user of the underlying `aos::EventLoop`.
+    /// Creates a new runtime for the underlying event loop.
     ///
     /// Consider using [`EventLoopRuntimeHolder.new`] instead, if you're working with an
-    /// `aos::EventLoop` owned (indirectly) by Rust code.
+    /// `aos::EventLoop` owned (indirectly) by Rust code or using [`EventLoopRuntime::with`] as a safe
+    /// alternative.
     ///
-    /// One common pattern is calling this in the constructor of an object whose lifetime is managed
-    /// by C++; C++ doesn't inherit the Rust lifetime but we do have a lot of C++ code that obeys
-    /// these rules implicitly.
+    /// One common pattern is wrapping the lifetime behind a higher-rank trait bound (such as
+    /// [`FnOnce`]). This would constraint the lifetime to `'static` and objects with `'event_loop`
+    /// returned by this runtime.
     ///
-    /// Call [`spawn`] to respond to events. The non-event-driven APIs may be used without calling
+    /// Call [`EventLoopRuntime::spawn`] to respond to events. The non-event-driven APIs may be used without calling
     /// this.
     ///
     /// This is an async runtime, but it's a somewhat unusual one. See the module-level
@@ -299,14 +275,11 @@
     /// together. It all boils down to choosing `'event_loop` correctly, which is very complicated.
     /// Here are the rules:
     ///
-    /// 1. The `aos::EventLoop` APIs, and any other consumer-facing APIs, of the underlying
-    ///    `aos::EventLoop` *must* be exclusively used by this object, and things it calls, for
-    ///    `'event_loop`.
-    /// 2. `'event_loop` extends until after the last time the underlying `aos::EventLoop` is run.
-    ///    This is often beyond the lifetime of this Rust `EventLoopRuntime` object.
-    /// 3. `'event_loop` must outlive this object, because this object stores references to the
+    /// 1. `'event_loop` extends until after the last time the underlying `aos::EventLoop` is run.
+    ///    **This is often beyond the lifetime of this Rust `EventLoopRuntime` object**.
+    /// 2. `'event_loop` must outlive this object, because this object stores references to the
     ///    underlying `aos::EventLoop`.
-    /// 4. Any other references stored in the underlying `aos::EventLoop` must be valid for
+    /// 3. Any other references stored in the underlying `aos::EventLoop` must be valid for
     ///    `'event_loop`. The easiest way to ensure this is by not using the `aos::EventLoop` before
     ///    passing it to this object.
     ///
@@ -314,13 +287,15 @@
     ///
     /// 1. The underlying `aos::EventLoop` must be dropped after this object.
     /// 2. This object will store various references valid for `'event_loop` with a duration of
-    ///   `'event_loop`, which is safe as long as they're both the same `'event_loop`. Note that
-    ///   this requires this type to be invariant with respect to `'event_loop`.
-    /// 3. `event_loop` (the pointer being passed in) is effectively `Pin`, which is also implied
-    ///    by the underlying `aos::EventLoop` C++ type.
-    /// 4. You cannot create multiple `EventLoopRuntime`s from the same underlying `aos::EventLoop`
-    ///    or otherwise use it from a different application. The first one may create
-    ///    mutable Rust references while the second one expects exclusive ownership, for example.
+    ///   `'event_loop`, which is safe as long as
+    ///
+    /// * `'event_loop` outlives the underlying event loop, and
+    /// * `'event_loop` references are not used once the event loop is destroyed
+    ///
+    /// Note that this requires this type to be invariant with respect to `'event_loop`. This can
+    /// be achieved by using [`EventLoopRuntime::with`] since `'event_loop` referenes can't leave
+    /// `fun` and the runtime holding `'event_loop` references will be destroyed before the event
+    /// loop.
     ///
     /// `aos::EventLoop`'s public API is exclusively for consumers of the event loop. Some
     /// subclasses extend this API. Additionally, all useful implementations of `aos::EventLoop`
@@ -330,18 +305,6 @@
     /// loop functions independently of the consuming functions in every way except lifetime of the
     /// `aos::EventLoop`, and may be used independently of `'event_loop`.
     ///
-    /// ## Discussion of the rules
-    ///
-    /// Rule 1 is similar to rule 3 (they're both similar to mutable borrowing), but rule 1 extends
-    /// for the entire lifetime of the object instead of being limited to the lifetime of an
-    /// individual borrow by an instance of this type. This is similar to the way [`Pin`]'s
-    /// estrictions extend for the entire lifetime of the object, until it is dropped.
-    ///
-    /// Rule 2 and corollaries 2 and 3 go together, and are essential for making [`spawn`]ed tasks
-    /// useful. The `aos::EventLoop` is full of indirect circular references, both within itself
-    /// and via all of the callbacks. This is sound if all of these references have the *exact
-    /// same* Rust lifetime, which is `'event_loop`.
-    ///
     /// ## Alternatives and why they don't work
     ///
     /// Making the argument `Pin<&'event_loop mut EventLoop>` would express some (but not all) of
@@ -350,7 +313,7 @@
     /// same object from C++, which is a common operation. See the module-level documentation for
     /// details.
     ///
-    /// [`spawn`]ed tasks need to hold `&'event_loop` references to things like channels. Using a
+    /// spawned tasks need to hold `&'event_loop` references to things like channels. Using a
     /// separate `'config` lifetime wouldn't change much; the tasks still need to do things which
     /// require them to not outlive something they don't control. This is fundamental to
     /// self-referential objects, which `aos::EventLoop` is based around, but Rust requires unsafe
@@ -358,48 +321,38 @@
     ///
     /// ## Final cautions
     ///
-    /// Following these rules is very tricky. Be very cautious calling this function. It exposes an
-    /// unbound lifetime, which means you should wrap it directly in a function that attaches a
-    /// correct lifetime.
-    pub unsafe fn new(event_loop: *mut ffi::aos::EventLoop) -> Self {
-        Self(
-            // SAFETY: We push all the validity requirements for this up to our caller.
-            unsafe { ffi::aos::EventLoopRuntime::new(event_loop) }.within_box(),
-            InvariantLifetime::default(),
-        )
+    /// Following these rules is very tricky. Be very cautious calling this function. The
+    /// exposed lifetime doesn't actually convey all the rules to the compiler. To the compiler,
+    /// `'event_loop` ends when this object is dropped which is not the case!
+    pub unsafe fn new(event_loop: &'event_loop CppEventLoopRuntime) -> Self {
+        Self(event_loop, InvariantLifetime::default())
     }
 
-    /// Creates a Rust wrapper from the underlying C++ object, with an unbound lifetime.
+    /// Safely builds a "constrained" EventLoopRuntime with `fun`.
     ///
-    /// This may never be useful, but it's here for this big scary comment to explain why it's not
-    /// useful.
-    ///
-    /// # Safety
-    ///
-    /// See [`new`] for safety restrictions on `'event_loop` when calling this. In particular, see
-    /// the note about how tricky doing this correctly is, and remember that for this function the
-    /// event loop in question isn't even an argument to this function so it's even trickier. Also
-    /// note that you cannot call this on the result of [`into_cpp`] without violating those
-    /// restrictions.
-    pub unsafe fn from_cpp(cpp: Pin<Box<ffi::aos::EventLoopRuntime>>) -> Self {
-        Self(cpp, InvariantLifetime::default())
-    }
-
-    /// Extracts the underlying C++ object, without the corresponding Rust lifetime. This is useful
-    /// to stop the propagation of Rust lifetimes without destroying the underlying object which
-    /// contains all the state.
-    ///
-    /// Note that you *cannot* call [`from_cpp`] on the result of this, because that will violate
-    /// [`from_cpp`]'s safety requirements.
-    pub fn into_cpp(self) -> Pin<Box<ffi::aos::EventLoopRuntime>> {
-        self.0
+    /// We constrain the scope of the `[EventLoopRuntime]` by tying it to **any** `'a` lifetime. The
+    /// idea is that the only things that satisfy this lifetime are either ``static` or produced by
+    /// the event loop itself with a '`event_loop` runtime.
+    pub fn with<F>(event_loop: &'event_loop CppEventLoopRuntime, fun: F)
+    where
+        F: for<'a> FnOnce(EventLoopRuntime<'a>),
+    {
+        // SAFETY: We satisfy the event loop lifetime constraint by scoping it inside of a higher-
+        // rank lifetime in FnOnce. This is similar to what is done in std::thread::scope, and the
+        // point is that `fun` can only assume that `'static` and types produced by this type with a
+        // 'event_loop lifetime are the only lifetimes that will satisfy `'a`. This is possible due
+        // to this type's invariance over its lifetime, otherwise, one could easily make a Subtype
+        // that, due to its shorter lifetime, would include things from its outer scope.
+        unsafe {
+            fun(Self::new(event_loop));
+        }
     }
 
     /// Returns the pointer passed into the constructor.
     ///
     /// The returned value should only be used for destroying it (_after_ `self` is dropped) or
     /// calling other C++ APIs.
-    pub fn raw_event_loop(&self) -> *mut ffi::aos::EventLoop {
+    pub fn raw_event_loop(&self) -> *mut CppEventLoop {
         self.0.event_loop()
     }
 
@@ -443,7 +396,7 @@
     /// will never complete. `task` will not be polled after the underlying `aos::EventLoop` exits.
     ///
     /// Note that task will be polled immediately, to give it a chance to initialize. If you want to
-    /// defer work until the event loop starts running, await [`on_run`] in the task.
+    /// defer work until the event loop starts running, await [`EventLoopRuntime::on_run`] in the task.
     ///
     /// # Panics
     ///
@@ -822,7 +775,7 @@
 ///
 /// We also run into some limitations in the borrow checker trying to implement `poll`, I think it's
 /// the same one mentioned here:
-/// https://blog.rust-lang.org/2022/08/05/nll-by-default.html#looking-forward-what-can-we-expect-for-the-borrow-checker-of-the-future
+/// <https://blog.rust-lang.org/2022/08/05/nll-by-default.html#looking-forward-what-can-we-expect-for-the-borrow-checker-of-the-future>
 /// We get around that one by moving the unbounded lifetime from the pointer dereference into the
 /// function with the if statement.
 // SAFETY: If this outlives the parent EventLoop, the C++ code will LOG(FATAL).
@@ -1165,11 +1118,6 @@
 pub struct RawSender(Pin<Box<ffi::aos::SenderForRust>>);
 
 impl RawSender {
-    fn buffer(&mut self) -> &mut [u8] {
-        // SAFETY: This is a valid slice, and `u8` doesn't have any alignment requirements.
-        unsafe { slice::from_raw_parts_mut(self.0.as_mut().data(), self.0.as_mut().size()) }
-    }
-
     /// Returns an object which can be used to build a message.
     ///
     /// # Examples
@@ -1192,6 +1140,7 @@
     /// # unsafe {
     /// let mut builder1 = sender.make_builder();
     /// builder1.fbb();
+    /// drop(builder1);
     /// let mut builder2 = sender.make_builder();
     /// let pong = PongBuilder::new(builder2.fbb()).finish();
     /// builder2.send(pong);
@@ -1211,11 +1160,14 @@
     /// # }
     /// ```
     pub fn make_builder(&mut self) -> RawBuilder {
-        // TODO(Brian): Actually use the provided buffer instead of just using its
-        // size to allocate a separate one.
-        //
-        // See https://github.com/google/flatbuffers/issues/7385.
-        let fbb = flatbuffers::FlatBufferBuilder::with_capacity(self.buffer().len());
+        // SAFETY: This is a valid slice, and `u8` doesn't have any alignment
+        // requirements. Additionally, the lifetime of the builder is tied to
+        // the lifetime of self so the buffer won't be accessible again until
+        // the builder is destroyed.
+        let allocator = ChannelPreallocatedAllocator::new(unsafe {
+            slice::from_raw_parts_mut(self.0.as_mut().data(), self.0.as_mut().size())
+        });
+        let fbb = FlatBufferBuilder::new_in(allocator);
         RawBuilder {
             raw_sender: self,
             fbb,
@@ -1226,11 +1178,13 @@
 /// Used for building a message. See [`RawSender::make_builder`] for details.
 pub struct RawBuilder<'sender> {
     raw_sender: &'sender mut RawSender,
-    fbb: flatbuffers::FlatBufferBuilder<'sender>,
+    fbb: FlatBufferBuilder<'sender, ChannelPreallocatedAllocator<'sender>>,
 }
 
 impl<'sender> RawBuilder<'sender> {
-    pub fn fbb(&mut self) -> &mut flatbuffers::FlatBufferBuilder<'sender> {
+    pub fn fbb(
+        &mut self,
+    ) -> &mut FlatBufferBuilder<'sender, ChannelPreallocatedAllocator<'sender>> {
         &mut self.fbb
     }
 
@@ -1243,12 +1197,7 @@
 
         use ffi::aos::RawSender_Error as FfiError;
         // SAFETY: This is a valid buffer we're passing.
-        match unsafe {
-            self.raw_sender
-                .0
-                .as_mut()
-                .CopyAndSend(data.as_ptr(), data.len())
-        } {
+        match self.raw_sender.0.as_mut().SendBuffer(data.len()) {
             FfiError::kOk => Ok(()),
             FfiError::kMessagesSentTooFast => Err(SendError::MessagesSentTooFast),
             FfiError::kInvalidRedzone => Err(SendError::InvalidRedzone),
@@ -1292,6 +1241,7 @@
     /// # fn compile_check(mut sender: aos_events_event_loop_runtime::Sender<Pong<'static>>) {
     /// let mut builder1 = sender.make_builder();
     /// builder1.fbb();
+    /// drop(builder1);
     /// let mut builder2 = sender.make_builder();
     /// let pong = PongBuilder::new(builder2.fbb()).finish();
     /// builder2.send(pong);
@@ -1327,7 +1277,9 @@
     for<'a> T: FollowWith<'a>,
     for<'a> <T as FollowWith<'a>>::Inner: Follow<'a>,
 {
-    pub fn fbb(&mut self) -> &mut flatbuffers::FlatBufferBuilder<'sender> {
+    pub fn fbb(
+        &mut self,
+    ) -> &mut FlatBufferBuilder<'sender, ChannelPreallocatedAllocator<'sender>> {
         self.0.fbb()
     }
 
@@ -1420,7 +1372,7 @@
 #[repr(transparent)]
 pub struct OnRun(Pin<Box<ffi::aos::OnRunForRust>>);
 
-impl Future for OnRun {
+impl Future for &'_ OnRun {
     type Output = ();
 
     fn poll(self: Pin<&mut Self>, _: &mut std::task::Context) -> Poll<()> {
@@ -1544,8 +1496,8 @@
 
 impl ExitHandle {
     /// Exits the EventLoops represented by this handle. You probably want to immediately return
-    /// from the context this is called in. Awaiting [`exit`] instead of using this function is an
-    /// easy way to do that.
+    /// from the context this is called in. Awaiting [`ExitHandle::exit`] instead of using this
+    /// function is an easy way to do that.
     pub fn exit_sync(mut self) {
         self.0.as_mut().unwrap().Exit();
     }
@@ -1564,3 +1516,44 @@
         Self(inner)
     }
 }
+
+pub struct ChannelPreallocatedAllocator<'a> {
+    buffer: &'a mut [u8],
+}
+
+impl<'a> ChannelPreallocatedAllocator<'a> {
+    pub fn new(buffer: &'a mut [u8]) -> Self {
+        Self { buffer }
+    }
+}
+
+#[derive(Debug, Error)]
+#[error("Can't allocate more memory with a fixed size allocator")]
+pub struct OutOfMemory;
+
+// SAFETY: Allocator follows the required behavior.
+unsafe impl Allocator for ChannelPreallocatedAllocator<'_> {
+    type Error = OutOfMemory;
+    fn grow_downwards(&mut self) -> Result<(), Self::Error> {
+        // Fixed size allocator can't grow.
+        Err(OutOfMemory)
+    }
+
+    fn len(&self) -> usize {
+        self.buffer.len()
+    }
+}
+
+impl Deref for ChannelPreallocatedAllocator<'_> {
+    type Target = [u8];
+
+    fn deref(&self) -> &Self::Target {
+        self.buffer
+    }
+}
+
+impl DerefMut for ChannelPreallocatedAllocator<'_> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.buffer
+    }
+}
diff --git a/aos/events/event_loop_runtime_test_lib.rs b/aos/events/event_loop_runtime_test_lib.rs
index 56dc9ef..acc162d 100644
--- a/aos/events/event_loop_runtime_test_lib.rs
+++ b/aos/events/event_loop_runtime_test_lib.rs
@@ -1,14 +1,26 @@
 //! These test helpers have to live in a separate file because autocxx only generates one set of
 //! outputs per file, and that needs to be the non-`#[cfg(test)]` stuff.
 
-use aos_events_event_loop_runtime::{EventLoop, EventLoopRuntime, Fetcher, RawFetcher};
+use aos_events_event_loop_runtime::{CppEventLoop as EventLoop, Fetcher, RawFetcher};
 use ping_rust_fbs::aos::examples::{root_as_ping, Ping};
 use pong_rust_fbs::aos::examples::{Pong, PongBuilder};
 
 mod tests {
+    use aos_events_event_loop_runtime::{EventLoopHolder, EventLoopRuntimeHolder};
+
     use super::*;
 
-    use std::cell::RefCell;
+    use std::{borrow::Borrow, cell::RefCell};
+
+    /// Represents a holder of the event loop that is managed in C++.
+    struct CppEventLoopHolder(*const EventLoop);
+
+    // SAFETY: We defer the requirement that the event loop is valid and won't move to C++.
+    unsafe impl EventLoopHolder for CppEventLoopHolder {
+        fn as_raw(&self) -> *const EventLoop {
+            self.0
+        }
+    }
 
     #[derive(Debug, Default)]
     struct GlobalState {
@@ -39,62 +51,65 @@
         GLOBAL_STATE.with(|g| g.borrow().on_run_count)
     }
 
-    pub struct TestApplication<'event_loop> {
-        _runtime: EventLoopRuntime<'event_loop>,
+    pub struct TestApplication {
+        _runtime: EventLoopRuntimeHolder<CppEventLoopHolder>,
         raw_ping_fetcher: RawFetcher,
     }
 
-    impl<'event_loop> TestApplication<'event_loop> {
-        fn new(mut runtime: EventLoopRuntime<'event_loop>) -> Self {
-            let ping_channel = runtime
-                .get_raw_channel("/test", "aos.examples.Ping")
-                .expect("Should have Ping channel");
-            let mut raw_ping_watcher = runtime.make_raw_watcher(ping_channel);
-            let mut raw_pong_sender = runtime.make_raw_sender(
-                runtime
-                    .get_raw_channel("/test", "aos.examples.Pong")
-                    .expect("Should have Pong channel"),
-            );
-            let on_run = runtime.on_run();
-            runtime.spawn(async move {
-                on_run.await;
-                GLOBAL_STATE.with(|g| {
-                    let g = &mut *g.borrow_mut();
-                    assert_eq!(g.creation_count, g.drop_count + 1);
-                    assert_eq!(g.drop_count, g.on_run_count);
-                    assert_eq!(g.drop_count, g.before_count);
-                    assert_eq!(g.drop_count, g.watcher_count);
-                    assert_eq!(g.drop_count, g.after_count);
-                    g.on_run_count += 1;
-                });
-                loop {
-                    let context = raw_ping_watcher.next().await;
-                    assert!(!context.monotonic_event_time().is_min_time());
-                    assert!(!context.data().is_none());
+    impl TestApplication {
+        fn new(event_loop: CppEventLoopHolder) -> Self {
+            let mut raw_ping_fetcher = None;
+            let runtime = EventLoopRuntimeHolder::new(event_loop, |runtime| {
+                let ping_channel = runtime
+                    .get_raw_channel("/test", "aos.examples.Ping")
+                    .expect("Should have Ping channel");
+                let mut raw_ping_watcher = runtime.make_raw_watcher(ping_channel);
+                let mut raw_pong_sender = runtime.make_raw_sender(
+                    runtime
+                        .get_raw_channel("/test", "aos.examples.Pong")
+                        .expect("Should have Pong channel"),
+                );
+                let on_run = runtime.on_run();
+                runtime.spawn(async move {
+                    on_run.borrow().await;
                     GLOBAL_STATE.with(|g| {
                         let g = &mut *g.borrow_mut();
                         assert_eq!(g.creation_count, g.drop_count + 1);
-                        assert_eq!(g.creation_count, g.on_run_count);
-                        assert_eq!(g.creation_count, g.before_count);
+                        assert_eq!(g.drop_count, g.on_run_count);
+                        assert_eq!(g.drop_count, g.before_count);
                         assert_eq!(g.drop_count, g.watcher_count);
                         assert_eq!(g.drop_count, g.after_count);
-                        g.watcher_count += 1;
+                        g.on_run_count += 1;
                     });
-                    let ping = root_as_ping(context.data().expect("should have the data"))
-                        .expect("Ping should be valid");
+                    loop {
+                        let context = raw_ping_watcher.next().await;
+                        assert!(!context.monotonic_event_time().is_min_time());
+                        assert!(!context.data().is_none());
+                        GLOBAL_STATE.with(|g| {
+                            let g = &mut *g.borrow_mut();
+                            assert_eq!(g.creation_count, g.drop_count + 1);
+                            assert_eq!(g.creation_count, g.on_run_count);
+                            assert_eq!(g.creation_count, g.before_count);
+                            assert_eq!(g.drop_count, g.watcher_count);
+                            assert_eq!(g.drop_count, g.after_count);
+                            g.watcher_count += 1;
+                        });
+                        let ping = root_as_ping(context.data().expect("should have the data"))
+                            .expect("Ping should be valid");
 
-                    let mut builder = raw_pong_sender.make_builder();
-                    let mut pong = PongBuilder::new(builder.fbb());
-                    pong.add_value(ping.value());
-                    let pong = pong.finish();
-                    // SAFETY: We're sending the correct type here.
-                    unsafe { builder.send(pong) }.expect("send should succeed");
-                }
+                        let mut builder = raw_pong_sender.make_builder();
+                        let mut pong = PongBuilder::new(builder.fbb());
+                        pong.add_value(ping.value());
+                        let pong = pong.finish();
+                        // SAFETY: We're sending the correct type here.
+                        unsafe { builder.send(pong) }.expect("send should succeed");
+                    }
+                });
+                raw_ping_fetcher = Some(runtime.make_raw_fetcher(ping_channel));
             });
-            let raw_ping_fetcher = runtime.make_raw_fetcher(ping_channel);
             Self {
                 _runtime: runtime,
-                raw_ping_fetcher,
+                raw_ping_fetcher: raw_ping_fetcher.unwrap(),
             }
         }
 
@@ -137,7 +152,7 @@
         }
     }
 
-    impl Drop for TestApplication<'_> {
+    impl Drop for TestApplication {
         fn drop(&mut self) {
             GLOBAL_STATE.with(|g| {
                 let g = &mut *g.borrow_mut();
@@ -151,61 +166,64 @@
         }
     }
 
-    unsafe fn make_test_application(event_loop: *mut EventLoop) -> Box<TestApplication<'static>> {
+    unsafe fn make_test_application(event_loop: *mut EventLoop) -> Box<TestApplication> {
         GLOBAL_STATE.with(|g| {
             let g = &mut *g.borrow_mut();
             g.creation_count += 1;
         });
-        Box::new(TestApplication::new(EventLoopRuntime::new(event_loop)))
+        Box::new(TestApplication::new(CppEventLoopHolder(event_loop)))
     }
 
-    pub struct TypedTestApplication<'event_loop> {
-        _runtime: EventLoopRuntime<'event_loop>,
+    pub struct TypedTestApplication {
+        _runtime: EventLoopRuntimeHolder<CppEventLoopHolder>,
         ping_fetcher: Fetcher<Ping<'static>>,
     }
 
-    impl<'event_loop> TypedTestApplication<'event_loop> {
-        fn new(mut runtime: EventLoopRuntime<'event_loop>) -> Self {
-            let mut ping_watcher = runtime.make_watcher::<Ping<'static>>("/test").unwrap();
-            let mut pong_sender = runtime.make_sender::<Pong<'static>>("/test").unwrap();
-            let on_run = runtime.on_run();
-            runtime.spawn(async move {
-                on_run.await;
-                GLOBAL_STATE.with(|g| {
-                    let g = &mut *g.borrow_mut();
-                    assert_eq!(g.creation_count, g.drop_count + 1);
-                    assert_eq!(g.drop_count, g.on_run_count);
-                    assert_eq!(g.drop_count, g.before_count);
-                    assert_eq!(g.drop_count, g.watcher_count);
-                    assert_eq!(g.drop_count, g.after_count);
-                    g.on_run_count += 1;
-                });
-                loop {
-                    let context = ping_watcher.next().await;
-                    assert!(!context.monotonic_event_time().is_min_time());
-                    assert!(!context.message().is_none());
+    impl TypedTestApplication {
+        fn new(event_loop: CppEventLoopHolder) -> Self {
+            let mut ping_fetcher = None;
+            let runtime = EventLoopRuntimeHolder::new(event_loop, |runtime| {
+                let mut ping_watcher = runtime.make_watcher::<Ping<'static>>("/test").unwrap();
+                let mut pong_sender = runtime.make_sender::<Pong<'static>>("/test").unwrap();
+                let on_run = runtime.on_run();
+                runtime.spawn(async move {
+                    on_run.borrow().await;
                     GLOBAL_STATE.with(|g| {
                         let g = &mut *g.borrow_mut();
                         assert_eq!(g.creation_count, g.drop_count + 1);
-                        assert_eq!(g.creation_count, g.on_run_count);
-                        assert_eq!(g.creation_count, g.before_count);
+                        assert_eq!(g.drop_count, g.on_run_count);
+                        assert_eq!(g.drop_count, g.before_count);
                         assert_eq!(g.drop_count, g.watcher_count);
                         assert_eq!(g.drop_count, g.after_count);
-                        g.watcher_count += 1;
+                        g.on_run_count += 1;
                     });
-                    let ping: Ping<'_> = context.message().unwrap();
+                    loop {
+                        let context = ping_watcher.next().await;
+                        assert!(!context.monotonic_event_time().is_min_time());
+                        assert!(!context.message().is_none());
+                        GLOBAL_STATE.with(|g| {
+                            let g = &mut *g.borrow_mut();
+                            assert_eq!(g.creation_count, g.drop_count + 1);
+                            assert_eq!(g.creation_count, g.on_run_count);
+                            assert_eq!(g.creation_count, g.before_count);
+                            assert_eq!(g.drop_count, g.watcher_count);
+                            assert_eq!(g.drop_count, g.after_count);
+                            g.watcher_count += 1;
+                        });
+                        let ping: Ping<'_> = context.message().unwrap();
 
-                    let mut builder = pong_sender.make_builder();
-                    let mut pong = PongBuilder::new(builder.fbb());
-                    pong.add_value(ping.value());
-                    let pong = pong.finish();
-                    builder.send(pong).expect("send should succeed");
-                }
+                        let mut builder = pong_sender.make_builder();
+                        let mut pong = PongBuilder::new(builder.fbb());
+                        pong.add_value(ping.value());
+                        let pong = pong.finish();
+                        builder.send(pong).expect("send should succeed");
+                    }
+                });
+                ping_fetcher = Some(runtime.make_fetcher("/test").unwrap());
             });
-            let ping_fetcher = runtime.make_fetcher("/test").unwrap();
             Self {
                 _runtime: runtime,
-                ping_fetcher,
+                ping_fetcher: ping_fetcher.unwrap(),
             }
         }
 
@@ -245,7 +263,7 @@
         }
     }
 
-    impl Drop for TypedTestApplication<'_> {
+    impl Drop for TypedTestApplication {
         fn drop(&mut self) {
             GLOBAL_STATE.with(|g| {
                 let g = &mut *g.borrow_mut();
@@ -259,44 +277,46 @@
         }
     }
 
-    unsafe fn make_typed_test_application(
-        event_loop: *mut EventLoop,
-    ) -> Box<TypedTestApplication<'static>> {
+    unsafe fn make_typed_test_application(event_loop: *mut EventLoop) -> Box<TypedTestApplication> {
         GLOBAL_STATE.with(|g| {
             let g = &mut *g.borrow_mut();
             g.creation_count += 1;
         });
-        Box::new(TypedTestApplication::new(EventLoopRuntime::new(event_loop)))
+        Box::new(TypedTestApplication::new(CppEventLoopHolder(event_loop)))
     }
 
-    struct PanicApplication<'event_loop> {
-        _runtime: EventLoopRuntime<'event_loop>,
+    struct PanicApplication {
+        _runtime: EventLoopRuntimeHolder<CppEventLoopHolder>,
     }
 
-    impl<'event_loop> PanicApplication<'event_loop> {
-        fn new(mut runtime: EventLoopRuntime<'event_loop>) -> Self {
-            runtime.spawn(async move {
-                panic!("Test Rust panic");
+    impl PanicApplication {
+        fn new(event_loop: CppEventLoopHolder) -> Self {
+            let runtime = EventLoopRuntimeHolder::new(event_loop, |runtime| {
+                runtime.spawn(async move {
+                    panic!("Test Rust panic");
+                });
             });
 
             Self { _runtime: runtime }
         }
     }
 
-    unsafe fn make_panic_application(event_loop: *mut EventLoop) -> Box<PanicApplication<'static>> {
-        Box::new(PanicApplication::new(EventLoopRuntime::new(event_loop)))
+    unsafe fn make_panic_application(event_loop: *mut EventLoop) -> Box<PanicApplication> {
+        Box::new(PanicApplication::new(CppEventLoopHolder(event_loop)))
     }
 
-    struct PanicOnRunApplication<'event_loop> {
-        _runtime: EventLoopRuntime<'event_loop>,
+    struct PanicOnRunApplication {
+        _runtime: EventLoopRuntimeHolder<CppEventLoopHolder>,
     }
 
-    impl<'event_loop> PanicOnRunApplication<'event_loop> {
-        fn new(mut runtime: EventLoopRuntime<'event_loop>) -> Self {
-            let on_run = runtime.on_run();
-            runtime.spawn(async move {
-                on_run.await;
-                panic!("Test Rust panic");
+    impl PanicOnRunApplication {
+        fn new(event_loop: CppEventLoopHolder) -> Self {
+            let runtime = EventLoopRuntimeHolder::new(event_loop, |runtime| {
+                let on_run = runtime.on_run();
+                runtime.spawn(async move {
+                    on_run.borrow().await;
+                    panic!("Test Rust panic");
+                });
             });
 
             Self { _runtime: runtime }
@@ -305,55 +325,49 @@
 
     unsafe fn make_panic_on_run_application(
         event_loop: *mut EventLoop,
-    ) -> Box<PanicOnRunApplication<'static>> {
-        Box::new(PanicOnRunApplication::new(EventLoopRuntime::new(
-            event_loop,
-        )))
+    ) -> Box<PanicOnRunApplication> {
+        Box::new(PanicOnRunApplication::new(CppEventLoopHolder(event_loop)))
     }
 
     #[cxx::bridge(namespace = "aos::events::testing")]
     mod ffi_bridge {
         extern "Rust" {
-            unsafe fn make_test_application(
-                event_loop: *mut EventLoop,
-            ) -> Box<TestApplication<'static>>;
+            unsafe fn make_test_application(event_loop: *mut EventLoop) -> Box<TestApplication>;
 
             unsafe fn make_typed_test_application(
                 event_loop: *mut EventLoop,
-            ) -> Box<TypedTestApplication<'static>>;
+            ) -> Box<TypedTestApplication>;
 
-            unsafe fn make_panic_application(
-                event_loop: *mut EventLoop,
-            ) -> Box<PanicApplication<'static>>;
+            unsafe fn make_panic_application(event_loop: *mut EventLoop) -> Box<PanicApplication>;
 
             unsafe fn make_panic_on_run_application(
                 event_loop: *mut EventLoop,
-            ) -> Box<PanicOnRunApplication<'static>>;
+            ) -> Box<PanicOnRunApplication>;
 
             fn completed_test_count() -> u32;
             fn started_test_count() -> u32;
         }
 
         extern "Rust" {
-            type TestApplication<'a>;
+            type TestApplication;
 
             fn before_sending(&mut self);
             fn after_sending(&mut self);
         }
 
         extern "Rust" {
-            type TypedTestApplication<'a>;
+            type TypedTestApplication;
 
             fn before_sending(&mut self);
             fn after_sending(&mut self);
         }
 
         extern "Rust" {
-            type PanicApplication<'a>;
+            type PanicApplication;
         }
 
         extern "Rust" {
-            type PanicOnRunApplication<'a>;
+            type PanicOnRunApplication;
         }
 
         unsafe extern "C++" {
diff --git a/aos/events/ping.rs b/aos/events/ping.rs
index 3226461..b285860 100644
--- a/aos/events/ping.rs
+++ b/aos/events/ping.rs
@@ -1,72 +1,24 @@
-use aos_configuration as config;
-use aos_events_event_loop_runtime::{EventLoopRuntime, Sender, Watcher};
-use aos_events_shm_event_loop::ShmEventLoop;
-use core::cell::Cell;
-use core::time::Duration;
-use futures::never::Never;
+use aos::configuration;
+use aos::events::shm_event_loop::ShmEventLoop;
+use aos::init::Init;
+use clap::Parser;
+use ping_lib::PingTask;
 use std::path::Path;
 
-use ping_rust_fbs::aos::examples as ping;
-use pong_rust_fbs::aos::examples as pong;
+/// Ping portion of a ping/pong system.
+#[derive(Parser, Debug)]
+struct App {
+    /// Time to sleep between pings.
+    #[arg(long, default_value_t = 10000, value_name = "MICROS")]
+    sleep: u64,
+}
 
 fn main() {
-    aos_init::init();
-    let config = config::read_config_from(Path::new("pingpong_config.json")).unwrap();
+    let app = App::init();
+    let config = configuration::read_config_from(Path::new("pingpong_config.json")).unwrap();
     let ping = PingTask::new();
     ShmEventLoop::new(&config).run_with(|runtime| {
-        runtime.spawn(ping.tasks(runtime));
+        runtime.set_realtime_priority(5);
+        runtime.spawn(ping.tasks(*runtime, app.sleep));
     });
 }
-
-#[derive(Debug)]
-struct PingTask {
-    counter: Cell<i32>,
-}
-
-impl PingTask {
-    pub fn new() -> Self {
-        Self {
-            counter: Cell::new(0),
-        }
-    }
-
-    /// Returns a future with all the tasks for the ping process
-    pub async fn tasks(&self, event_loop: &EventLoopRuntime<'_>) -> Never {
-        futures::join!(self.ping(event_loop), self.handle_pong(event_loop));
-        unreachable!("Let's hope `never_type` gets stabilized soon :)");
-    }
-
-    async fn ping(&self, event_loop: &EventLoopRuntime<'_>) -> Never {
-        // The sender is used to send messages back to the pong channel.
-        let mut ping_sender: Sender<ping::Ping> = event_loop.make_sender("/test").unwrap();
-        let mut interval = event_loop.add_interval(Duration::from_secs(1));
-
-        event_loop.on_run().await;
-        loop {
-            interval.tick().await;
-            self.counter.set(self.counter.get() + 1);
-            let mut builder = ping_sender.make_builder();
-            let mut ping = ping::PingBuilder::new(builder.fbb());
-            let iter = self.counter.get();
-            ping.add_value(iter);
-            ping.add_send_time(event_loop.monotonic_now().into());
-            let ping = ping.finish();
-            builder.send(ping).expect("Can't send ping");
-        }
-    }
-
-    async fn handle_pong(&self, event_loop: &EventLoopRuntime<'_>) -> Never {
-        // The watcher gives us incoming ping messages.
-        let mut pong_watcher: Watcher<pong::Pong> = event_loop.make_watcher("/test").unwrap();
-
-        event_loop.on_run().await;
-        loop {
-            let pong = dbg!(pong_watcher.next().await);
-            assert_eq!(
-                pong.message().unwrap().value(),
-                self.counter.get(),
-                "Missed a reply"
-            );
-        }
-    }
-}
diff --git a/aos/events/ping_lib.rs b/aos/events/ping_lib.rs
new file mode 100644
index 0000000..ce4d844
--- /dev/null
+++ b/aos/events/ping_lib.rs
@@ -0,0 +1,64 @@
+use aos::events::event_loop_runtime::{EventLoopRuntime, Sender, Watcher};
+use core::cell::Cell;
+use core::time::Duration;
+use futures::never::Never;
+use std::borrow::Borrow;
+
+use ping_rust_fbs::aos::examples as ping;
+use pong_rust_fbs::aos::examples as pong;
+
+#[derive(Debug)]
+pub struct PingTask {
+    counter: Cell<i32>,
+}
+
+impl PingTask {
+    pub fn new() -> Self {
+        Self {
+            counter: Cell::new(0),
+        }
+    }
+
+    /// Returns a future with all the tasks for the ping process
+    #[allow(unreachable_code)]
+    pub async fn tasks(&self, event_loop: EventLoopRuntime<'_>, sleep: u64) -> Never {
+        futures::join!(self.ping(&event_loop, sleep), self.handle_pong(&event_loop));
+        unreachable!("Let's hope `never_type` gets stabilized soon :)");
+    }
+
+    pub async fn ping(&self, event_loop: &EventLoopRuntime<'_>, sleep: u64) -> Never {
+        // The sender is used to send messages back to the pong channel.
+        let mut ping_sender: Sender<ping::Ping> = event_loop.make_sender("/test").unwrap();
+        let mut interval = event_loop.add_interval(Duration::from_micros(sleep));
+
+        let on_run = event_loop.on_run();
+        on_run.borrow().await;
+
+        loop {
+            interval.tick().await;
+            self.counter.set(self.counter.get() + 1);
+            let mut builder = ping_sender.make_builder();
+            let mut ping = ping::PingBuilder::new(builder.fbb());
+            let iter = self.counter.get();
+            log::trace!("Ping: {iter}");
+            ping.add_value(iter);
+            ping.add_send_time(event_loop.monotonic_now().into());
+            let ping = ping.finish();
+            builder.send(ping).expect("Can't send ping");
+        }
+    }
+
+    pub async fn handle_pong(&self, event_loop: &EventLoopRuntime<'_>) -> Never {
+        // The watcher gives us incoming ping messages.
+        let mut pong_watcher: Watcher<pong::Pong> = event_loop.make_watcher("/test").unwrap();
+
+        let on_run = event_loop.on_run();
+        on_run.borrow().await;
+        loop {
+            let pong = pong_watcher.next().await;
+            let pong = pong.message().unwrap();
+            log::trace!("Got pong: {}", pong.value());
+            assert_eq!(pong.value(), self.counter.get(), "Missed a reply");
+        }
+    }
+}
diff --git a/aos/events/pingpong_test.rs b/aos/events/pingpong_test.rs
new file mode 100644
index 0000000..70d8772
--- /dev/null
+++ b/aos/events/pingpong_test.rs
@@ -0,0 +1,114 @@
+#[cfg(test)]
+mod tests {
+    use std::{cell::Cell, time::Duration};
+
+    use aos::configuration::read_config_from;
+    use aos::events::event_loop_runtime::{EventLoopRuntimeHolder, Watcher};
+    use aos::events::simulated_event_loop::{SimulatedEventLoopFactory, SimulatedEventLoopHolder};
+    use aos::testing::init::test_init;
+    use ping_lib::PingTask;
+    use ping_rust_fbs::aos::examples as ping;
+    use pong_rust_fbs::aos::examples as pong;
+    use runfiles::Runfiles;
+
+    // We use this trait to simplify leaking memory. For now, the event loop only allows
+    // data with a `'static` lifetime. Until that restriction is lifted, we may leak
+    // some memory in tests.
+    trait Leak: Sized {
+        fn leak(self) -> &'static mut Self {
+            Box::leak(Box::new(self))
+        }
+    }
+
+    impl<T> Leak for T {}
+
+    #[allow(unused)]
+    struct PingPongTest {
+        ping_event_loop: EventLoopRuntimeHolder<SimulatedEventLoopHolder>,
+        pong_event_loop: EventLoopRuntimeHolder<SimulatedEventLoopHolder>,
+        event_loop_factory: SimulatedEventLoopFactory<'static>,
+    }
+
+    impl PingPongTest {
+        pub fn init() -> PingPongTest {
+            test_init();
+            let r = Runfiles::create().unwrap();
+            let config =
+                read_config_from(&r.rlocation("org_frc971/aos/events/pingpong_config.json"))
+                    .unwrap()
+                    .leak();
+            let mut event_loop_factory = SimulatedEventLoopFactory::new(config);
+
+            let ping_event_loop = event_loop_factory.make_runtime("ping", None, |runtime| {
+                let ping = PingTask::new();
+                runtime.spawn(async move { ping.tasks(runtime, 10000).await });
+            });
+
+            let pong_event_loop = event_loop_factory.make_runtime("pong", None, |runtime| {
+                runtime.spawn(async move { pong_lib::pong(runtime).await })
+            });
+            PingPongTest {
+                event_loop_factory,
+                ping_event_loop,
+                pong_event_loop,
+            }
+        }
+
+        pub fn event_loop(&mut self) -> &mut SimulatedEventLoopFactory<'static> {
+            &mut self.event_loop_factory
+        }
+    }
+
+    #[test]
+    fn starts() {
+        let mut pingpong = PingPongTest::init();
+        pingpong.event_loop().run_for(Duration::from_secs(10));
+    }
+
+    #[test]
+    fn always_replies() {
+        let mut pingpong = PingPongTest::init();
+
+        // For now, the simulated event loop requires all references in the tasks
+        // to be `'static`, so we leak them for now until the restriction is lifted.
+        let ping_count: &Cell<i32> = Cell::new(1).leak();
+        let pong_count: &Cell<i32> = Cell::new(1).leak();
+
+        let _test_runtime = pingpong.event_loop().make_runtime("test", None, |runtime| {
+            let count_pings = async move {
+                let mut ping_watcher: Watcher<ping::Ping> = runtime.make_watcher("/test").unwrap();
+                loop {
+                    let ping = ping_watcher.next().await;
+                    assert_eq!(ping.message().unwrap().value(), ping_count.get());
+                    ping_count.set(ping_count.get() + 1);
+                }
+            };
+            let count_pongs = async move {
+                let mut pong_watcher: Watcher<pong::Pong> = runtime.make_watcher("/test").unwrap();
+                loop {
+                    let pong = pong_watcher.next().await;
+                    assert_eq!(pong.message().unwrap().value(), pong_count.get());
+                    pong_count.set(pong_count.get() + 1);
+                }
+            };
+
+            runtime.spawn(async move {
+                futures::join!(count_pings, count_pongs);
+                unreachable!();
+            });
+        });
+
+        pingpong.event_loop().run_for(Duration::from_secs(10));
+
+        // We run at t=0 and t=10 seconds, which means we run 1 extra time (Note that we started
+        // the count at 1, not 0).
+        assert_eq!(ping_count.get(), 1002);
+        assert_eq!(pong_count.get(), 1002);
+    }
+}
+
+// TODO(adam.snaider): Remove once we don't leak.
+#[no_mangle]
+extern "C" fn __asan_default_options() -> *const u8 {
+    "detect_leaks=0\0".as_ptr()
+}
diff --git a/aos/events/pong.rs b/aos/events/pong.rs
index d2859a8..c2c316b 100644
--- a/aos/events/pong.rs
+++ b/aos/events/pong.rs
@@ -1,38 +1,16 @@
-use aos_configuration as config;
-use aos_events_event_loop_runtime::{EventLoopRuntime, Sender, Watcher};
-use aos_events_shm_event_loop::ShmEventLoop;
-use futures::never::Never;
+use aos::configuration;
+use aos::events::shm_event_loop::ShmEventLoop;
+use aos::init::{DefaultApp, Init};
 use std::path::Path;
 
-use ping_rust_fbs::aos::examples as ping;
-use pong_rust_fbs::aos::examples as pong;
+use pong_lib::pong;
 
 fn main() {
-    aos_init::init();
-    let config = config::read_config_from(Path::new("pingpong_config.json")).unwrap();
+    let _ = DefaultApp::init();
+    let config = configuration::read_config_from(Path::new("pingpong_config.json")).unwrap();
     ShmEventLoop::new(&config).run_with(|runtime| {
-        let task = pong(runtime);
+        let task = pong(*runtime);
+        runtime.set_realtime_priority(5);
         runtime.spawn(task);
     });
 }
-
-/// Responds to ping messages with an equivalent pong.
-async fn pong(event_loop: &EventLoopRuntime<'_>) -> Never {
-    // The watcher gives us incoming ping messages.
-    let mut ping_watcher: Watcher<ping::Ping> = event_loop.make_watcher("/test").unwrap();
-
-    // The sender is used to send messages back to the pong channel.
-    let mut pong_sender: Sender<pong::Pong> = event_loop.make_sender("/test").unwrap();
-
-    event_loop.on_run().await;
-    loop {
-        let ping = dbg!(ping_watcher.next().await);
-
-        let mut builder = pong_sender.make_builder();
-        let mut pong = pong::PongBuilder::new(builder.fbb());
-        pong.add_value(ping.message().unwrap().value());
-        pong.add_initial_send_time(event_loop.monotonic_now().into());
-        let pong = pong.finish();
-        builder.send(pong).expect("Can't send pong reponse");
-    }
-}
diff --git a/aos/events/pong_lib.rs b/aos/events/pong_lib.rs
new file mode 100644
index 0000000..8a0587a
--- /dev/null
+++ b/aos/events/pong_lib.rs
@@ -0,0 +1,30 @@
+use aos::events::event_loop_runtime::{EventLoopRuntime, Sender, Watcher};
+use futures::never::Never;
+use std::borrow::Borrow;
+
+use ping_rust_fbs::aos::examples as ping;
+use pong_rust_fbs::aos::examples as pong;
+
+/// Responds to ping messages with an equivalent pong.
+pub async fn pong(event_loop: EventLoopRuntime<'_>) -> Never {
+    // The watcher gives us incoming ping messages.
+    let mut ping_watcher: Watcher<ping::Ping> = event_loop.make_watcher("/test").unwrap();
+
+    // The sender is used to send messages back to the pong channel.
+    let mut pong_sender: Sender<pong::Pong> = event_loop.make_sender("/test").unwrap();
+
+    let on_run = event_loop.on_run();
+    on_run.borrow().await;
+    loop {
+        let ping = ping_watcher.next().await;
+        let ping = ping.message().unwrap();
+        log::info!("Got ping: {}", ping.value());
+
+        let mut builder = pong_sender.make_builder();
+        let mut pong = pong::PongBuilder::new(builder.fbb());
+        pong.add_value(ping.value());
+        pong.add_initial_send_time(event_loop.monotonic_now().into());
+        let pong = pong.finish();
+        builder.send(pong).expect("Can't send pong reponse");
+    }
+}
diff --git a/aos/events/shm_event_loop.rs b/aos/events/shm_event_loop.rs
index cebf81b..ee6df18 100644
--- a/aos/events/shm_event_loop.rs
+++ b/aos/events/shm_event_loop.rs
@@ -1,6 +1,7 @@
 pub use aos_configuration::{Configuration, ConfigurationExt};
-pub use aos_events_event_loop_runtime::EventLoop;
-pub use aos_events_event_loop_runtime::{CppExitHandle, EventLoopRuntime, ExitHandle};
+pub use aos_events_event_loop_runtime::{
+    CppEventLoop, CppEventLoopRuntime, CppExitHandle, EventLoopRuntime, ExitHandle,
+};
 
 use aos_configuration_fbs::aos::Configuration as RustConfiguration;
 use aos_flatbuffers::{transmute_table_to, Flatbuffer};
@@ -20,7 +21,7 @@
 
 extern_cpp_type!("aos::ExitHandle", crate::CppExitHandle)
 extern_cpp_type!("aos::Configuration", crate::Configuration)
-extern_cpp_type!("aos::EventLoop", crate::EventLoop)
+extern_cpp_type!("aos::EventLoop", crate::CppEventLoop)
 );
 
 /// A Rust-owned C++ `ShmEventLoop` object.
@@ -65,6 +66,7 @@
     /// # use aos_events_shm_event_loop::*;
     /// use ping_rust_fbs::aos::examples as ping;
     /// use pong_rust_fbs::aos::examples as pong;
+    /// use std::borrow::Borrow;
     /// use std::cell::Cell;
     /// use std::path::Path;
     /// use aos_configuration::read_config_from;
@@ -81,7 +83,7 @@
     ///   let on_run = runtime.on_run();
     ///   // Sends a single ping message.
     ///   let send_task = async move {
-    ///     on_run.await;
+    ///     on_run.borrow().await;
     ///     let mut builder = sender.make_builder();
     ///     let mut ping = ping::PingBuilder::new(builder.fbb());
     ///     ping.add_value(10);
@@ -163,16 +165,16 @@
     /// ```
     pub fn run_with<'env, F>(mut self, fun: F)
     where
-        F: for<'event_loop> FnOnce(
-            &'event_loop mut Scoped<'event_loop, 'env, EventLoopRuntime<'event_loop>>,
-        ),
+        F: for<'event_loop> FnOnce(Scoped<'event_loop, 'env, EventLoopRuntime<'event_loop>>),
     {
         // SAFETY: The runtime and the event loop (i.e. self) both get destroyed at the end of this
         // scope: first the runtime followed by the event loop. The runtime gets exclusive access
         // during initialization in `fun` while the event loop remains unused.
-        let runtime = unsafe { EventLoopRuntime::new(self.inner.as_mut().event_loop_mut()) };
-        let mut runtime = Scoped::new(runtime);
-        fun(&mut runtime);
+        let cpp_runtime =
+            unsafe { CppEventLoopRuntime::new(self.inner.as_mut().event_loop_mut()).within_box() };
+        let runtime = unsafe { EventLoopRuntime::new(&cpp_runtime) };
+        let runtime = Scoped::new(runtime);
+        fun(runtime);
         self.run();
     }
 
@@ -197,6 +199,7 @@
 /// any `'scope`, this allows the compiler to propagate lifetime bounds which
 /// outlive any of the possible `'scope`. This is the simplest way to express
 /// this concept to the compiler right now.
+#[derive(Clone, Copy)]
 pub struct Scoped<'scope, 'env: 'scope, T: 'scope> {
     data: T,
     _env: PhantomData<fn(&'env ()) -> &'env ()>,
@@ -237,6 +240,7 @@
     use aos_events_event_loop_runtime::{Sender, Watcher};
     use aos_test_init::test_init;
     use ping_rust_fbs::aos::examples as ping;
+    use std::borrow::Borrow;
     use std::sync::atomic::{AtomicUsize, Ordering};
     use std::sync::Barrier;
 
@@ -261,11 +265,11 @@
                 let mut event_loop = ShmEventLoop::new(config);
                 let exit_handle = event_loop.make_exit_handle();
                 event_loop.run_with(|runtime| {
-                    runtime.spawn(async {
+                    runtime.spawn(async move {
                         let mut watcher: Watcher<ping::Ping> = runtime
                             .make_watcher("/test")
                             .expect("Can't create `Ping` watcher");
-                        runtime.on_run().await;
+                        runtime.on_run().borrow().await;
                         barrier.wait();
                         let ping = watcher.next().await;
                         assert_eq!(ping.message().unwrap().value(), VALUE);
@@ -278,11 +282,11 @@
                 let mut event_loop = ShmEventLoop::new(config);
                 let exit_handle = event_loop.make_exit_handle();
                 event_loop.run_with(|runtime| {
-                    runtime.spawn(async {
+                    runtime.spawn(async move {
                         let mut sender: Sender<ping::Ping> = runtime
                             .make_sender("/test")
                             .expect("Can't create `Ping` sender");
-                        runtime.on_run().await;
+                        runtime.on_run().borrow().await;
                         // Give the waiting thread a chance to start.
                         barrier.wait();
                         let mut sender = sender.make_builder();
diff --git a/aos/events/simulated_event_loop.rs b/aos/events/simulated_event_loop.rs
index 90c4c86..c75f34e 100644
--- a/aos/events/simulated_event_loop.rs
+++ b/aos/events/simulated_event_loop.rs
@@ -1,11 +1,11 @@
-use std::{marker::PhantomData, pin::Pin, ptr};
+use std::{marker::PhantomData, pin::Pin, ptr, time::Duration};
 
 use autocxx::WithinBox;
 use cxx::UniquePtr;
 
 pub use aos_configuration::{Channel, Configuration, ConfigurationExt, Node};
 use aos_configuration_fbs::aos::Configuration as RustConfiguration;
-pub use aos_events_event_loop_runtime::{CppExitHandle, EventLoop, ExitHandle};
+pub use aos_events_event_loop_runtime::{CppEventLoop, CppExitHandle, ExitHandle};
 use aos_events_event_loop_runtime::{EventLoopHolder, EventLoopRuntime, EventLoopRuntimeHolder};
 use aos_flatbuffers::{transmute_table_to, Flatbuffer};
 
@@ -20,7 +20,7 @@
 extern_cpp_type!("aos::ExitHandle", crate::CppExitHandle)
 extern_cpp_type!("aos::Configuration", crate::Configuration)
 extern_cpp_type!("aos::Node", crate::Node)
-extern_cpp_type!("aos::EventLoop", crate::EventLoop)
+extern_cpp_type!("aos::EventLoop", crate::CppEventLoop)
 );
 
 /// A Rust-owned C++ `SimulatedEventLoopFactory` object.
@@ -60,9 +60,9 @@
     /// Creates a Rust-owned EventLoop.
     ///
     /// You probably don't want to call this directly if you're creating a Rust application. This
-    /// is intended for creating C++ applications. Use [`make_runtime`] instead when creating Rust
+    /// is intended for creating C++ applications. Use [`Self::make_runtime`] instead when creating Rust
     /// applications.
-    pub fn make_event_loop(&mut self, name: &str, node: Option<&Node>) -> UniquePtr<EventLoop> {
+    pub fn make_event_loop(&mut self, name: &str, node: Option<&Node>) -> UniquePtr<CppEventLoop> {
         // SAFETY:
         // * `self` has a valid C++ object.
         // * C++ doesn't need the lifetimes of `name` or `node` to last any longer than this method
@@ -77,7 +77,7 @@
         }
     }
 
-    /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying [`EventLoop`].
+    /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying [`CppEventLoop`].
     ///
     /// All setup must be performed with `fun`, which is called before this function returns. `fun`
     /// may create further objects to use in async functions via [`EventLoop.spawn`] etc, but it is
@@ -91,7 +91,7 @@
         fun: F,
     ) -> EventLoopRuntimeHolder<SimulatedEventLoopHolder>
     where
-        F: for<'event_loop> FnOnce(&mut EventLoopRuntime<'event_loop>),
+        F: for<'event_loop> FnOnce(EventLoopRuntime<'event_loop>),
     {
         let event_loop = self.make_event_loop(name, node);
         // SAFETY: We just created this EventLoop, so we are the exclusive owner of it.
@@ -107,16 +107,25 @@
         self.as_mut().Run();
     }
 
+    pub fn run_for(&mut self, duration: Duration) {
+        self.as_mut().RunFor(
+            duration
+                .as_nanos()
+                .try_into()
+                .expect("Out of range: Internal clock uses 64 bits"),
+        );
+    }
+
     // TODO(Brian): Expose OnStartup. Just take a callback for creating things, and rely on
     // dropping the created objects instead of OnShutdown.
     // pub fn spawn_on_startup(&mut self, spawner: impl FnMut());
 }
 
-pub struct SimulatedEventLoopHolder(UniquePtr<EventLoop>);
+pub struct SimulatedEventLoopHolder(UniquePtr<CppEventLoop>);
 
 impl SimulatedEventLoopHolder {
     /// SAFETY: `event_loop` must be the exclusive owner of the underlying EventLoop.
-    pub unsafe fn new(event_loop: UniquePtr<EventLoop>) -> Self {
+    pub unsafe fn new(event_loop: UniquePtr<CppEventLoop>) -> Self {
         Self(event_loop)
     }
 }
@@ -124,12 +133,11 @@
 // SAFETY: The UniquePtr functions we're using here mirror most of the EventLoopHolder requirements
 // exactly. Safety requirements on [`SimulatedEventLoopHolder.new`] take care of the rest.
 unsafe impl EventLoopHolder for SimulatedEventLoopHolder {
-    fn into_raw(self) -> *mut ffi::aos::EventLoop {
-        self.0.into_raw()
-    }
-
-    unsafe fn from_raw(raw: *mut ffi::aos::EventLoop) -> Self {
-        Self(UniquePtr::from_raw(raw))
+    fn as_raw(&self) -> *const CppEventLoop {
+        self.0
+            .as_ref()
+            .map(|event_loop| event_loop as *const CppEventLoop)
+            .unwrap_or(core::ptr::null())
     }
 }
 
diff --git a/aos/events/simulated_event_loop_for_rust.h b/aos/events/simulated_event_loop_for_rust.h
index a8d630c..663d163 100644
--- a/aos/events/simulated_event_loop_for_rust.h
+++ b/aos/events/simulated_event_loop_for_rust.h
@@ -17,6 +17,9 @@
   }
 
   void Run() { factory_.Run(); }
+  void RunFor(int64_t nanos) {
+    factory_.RunFor(std::chrono::nanoseconds(nanos));
+  }
 
   std::unique_ptr<ExitHandle> MakeExitHandle() {
     return factory_.MakeExitHandle();
diff --git a/aos/init.cc b/aos/init.cc
index 3ca7314..579ad33 100644
--- a/aos/init.cc
+++ b/aos/init.cc
@@ -45,25 +45,6 @@
   initialized = true;
 }
 
-void InitFromRust(const char *argv0) {
-  CHECK(!IsInitialized()) << "Only initialize once.";
-
-  FLAGS_logtostderr = true;
-
-  google::InitGoogleLogging(argv0);
-
-  // TODO(Brian): Provide a way for Rust to configure C++ flags.
-  char fake_argv0_val[] = "rust";
-  char *fake_argv0 = fake_argv0_val;
-  char **fake_argv = &fake_argv0;
-  int fake_argc = 1;
-  gflags::ParseCommandLineFlags(&fake_argc, &fake_argv, true);
-
-  // TODO(Brian): Where should Rust binaries be configured to write coredumps?
-
-  // TODO(Brian): Figure out what to do with allocator hooks for C++ and Rust.
-
-  initialized = true;
-}
+void MarkInitialized() { initialized = true; }
 
 }  // namespace aos
diff --git a/aos/init.h b/aos/init.h
index 29480eb..b9270b3 100644
--- a/aos/init.h
+++ b/aos/init.h
@@ -10,10 +10,10 @@
 // ShmEventLoop can confirm the world was initialized before running.
 bool IsInitialized();
 
-// A special initialization function that initializes the C++ parts in a way
-// compatible with Rust. This requires careful coordination with `:init_rs`, do
-// not use it from anywhere else.
-void InitFromRust(const char *argv0);
+// Marks the system as initialized. This is only meant to be used from
+// init_for_rust. DO NOT call this from anywhere else, use InitGoogle
+// instead.
+void MarkInitialized();
 
 }  // namespace aos
 
diff --git a/aos/init.rs b/aos/init.rs
index 13066f1..fe0408d 100644
--- a/aos/init.rs
+++ b/aos/init.rs
@@ -1,20 +1,275 @@
-use std::{ffi::CString, sync::Once};
+//! AOS Initialization
+//!
+//! This module "links" the C++ library and the Rust application together.
+//! In particular it provides the [`Init`] trait which is implemented for
+//! any struct that implements [`clap::Parser`]. The reason for this is that
+//! an important part of initializing the C++ library involves setting the
+//! gFlags which get resolved dynamically thanks to their reflection API.
+//!
+//! # Examples
+//!
+//! ```no_run
+//! use aos_init::Init;
+//! use clap::Parser;
+//!
+//! #[derive(Parser, Debug)]
+//! struct App {
+//!     /// Time to sleep between pings.
+//!     #[arg(long, default_value_t = 10000, value_name = "MICROS")]
+//!     sleep: u64,
+//! }
+//!
+//! fn main() {
+//!     // Initializes AOS and returns the App struct with the parsed CLI flags
+//!     let app: App = App::init();
+//!     // At this point your flags are parsed and AOS is initialized.
+//! }
+//! ```
+//! You can also use [`DefaultApp`] to avoid having to specify your own CLI options if you don't
+//! need them. For example:
+//!
+//! ```no_run
+//! use aos_init::{DefaultApp, Init};
+//! use clap::Parser;
+//!
+//! fn main() {
+//!     // Initializes AOS. DefaultApp doesn't have any flags to parse.
+//!     let _ = DefaultApp::init();
+//!     // At this point AOS is initialized and you can create event loop.
+//! }
+//!```
+
+use std::{
+    env,
+    ffi::{CString, OsStr, OsString},
+    os::unix::prelude::OsStrExt,
+    sync::Once,
+};
+
+use clap::{
+    error::{ContextKind, ContextValue},
+    Arg, ArgAction, Error, Parser,
+};
 
 autocxx::include_cpp! (
-#include "aos/init.h"
+#include "aos/init_for_rust.h"
 
 safety!(unsafe)
 
 generate!("aos::InitFromRust")
+generate!("aos::GetCppFlags")
+generate!("aos::FlagInfo")
+generate!("aos::SetCommandLineOption")
+generate!("aos::GetCommandLineOption")
 );
 
-/// Initializes AOS.
-pub fn init() {
-    static ONCE: Once = Once::new();
-    ONCE.call_once(|| {
-        let argv0 = std::env::args().next().expect("must have argv[0]");
-        let argv0 = CString::new(argv0).expect("argv[0] may not have NUL");
-        // SAFETY: argv0 is a NUL-terminated string.
-        unsafe { ffi::aos::InitFromRust(argv0.as_ptr()) };
-    });
+// Intended to be used only from here and test_init. Don't use it anywhere else please.
+#[doc(hidden)]
+pub mod internal {
+    use super::*;
+    /// Generic initialization for production and tests.
+    ///
+    /// Sets up the C++ side of things. Notably, it doesn't setup the command line flags.
+    pub fn init() {
+        static ONCE: Once = Once::new();
+        ONCE.call_once(|| {
+            // We leak the `CString` with `into_raw`. It's not sound for C++ to free
+            // it but `InitGoogleLogging` requries that it is long-lived.
+            let argv0 = std::env::args()
+                .map(|arg| CString::new(arg).expect("Arg may not have NUL"))
+                .next()
+                .expect("Missing argv[0]?")
+                .into_raw();
+            // SAFETY: argv0 is a well-defined CString.
+            unsafe {
+                ffi::aos::InitFromRust(argv0);
+            }
+        });
+    }
+}
+
+/// An application that doesn't need custom command line flags.
+///
+/// If you need your own command line flags, use any struct that derives [`clap::Parser`] instead.
+#[derive(Parser, Debug)]
+pub struct DefaultApp {}
+
+/// Trait used to append C++ gFlags to a clap CLI.
+pub trait Init: Parser {
+    /// Initializes an AOS application.
+    ///
+    /// Parses the command line flags and runs the initialization logic.
+    fn init() -> Self {
+        let this = Self::parse_with_cpp_flags();
+        // Rust logs to stderr by default. Make that true for C++ as that will be easier than
+        // managing one or multiple files across FFI. We can pipe the stderr to a file to get
+        // a log file if we want.
+        CxxFlag::set_option("logtostderr", "true".as_ref())
+            .expect("Error setting C++ flag: logtostderr");
+        internal::init();
+        // Non-test initialization below
+        env_logger::init();
+        this
+    }
+
+    /// Parses the comannd line arguments while also setting the C++ gFlags.
+    fn parse_with_cpp_flags() -> Self {
+        Self::parse_with_cpp_flags_from(env::args_os())
+    }
+
+    /// Like [`Init::parse_with_cpp_flags`] but read from an iterator.
+    fn parse_with_cpp_flags_from<I, T>(itr: I) -> Self
+    where
+        I: IntoIterator<Item = T>,
+        T: Into<OsString> + Clone,
+    {
+        let cxxflags = ffi::aos::GetCppFlags();
+        let cxxflags: Vec<CxxFlag> = cxxflags
+            .iter()
+            .map(|flag| CxxFlag::from(flag))
+            .filter(|flag| flag.name != "help" && flag.name != "version")
+            .collect();
+
+        let mut command = Self::command()
+            .next_help_heading("Flags from C++")
+            .args(cxxflags.iter().cloned());
+
+        let matches = command.clone().get_matches_from(itr);
+
+        for cxxflag in cxxflags {
+            let Some(mut value) = matches.get_raw(&cxxflag.name) else {
+                continue;
+            };
+            // We grab the last match as GFlags does.
+            let value = value.next_back().unwrap();
+            cxxflag.set(value).unwrap_or_else(|_| {
+                let mut error = Error::new(clap::error::ErrorKind::InvalidValue);
+
+                // Let user know how they messed up.
+                error.insert(
+                    ContextKind::InvalidArg,
+                    ContextValue::String(format!("--{}", cxxflag.name)),
+                );
+                error.insert(
+                    ContextKind::InvalidValue,
+                    ContextValue::String(
+                        value
+                            .to_owned()
+                            .into_string()
+                            .expect("Invalid UTF-8 String"),
+                    ),
+                );
+                error.format(&mut command).exit()
+            })
+        }
+
+        match Self::from_arg_matches(&matches) {
+            Ok(flags) => flags,
+            Err(e) => e.format(&mut command).exit(),
+        }
+    }
+}
+
+impl<T: Parser> Init for T {}
+
+#[derive(Clone)]
+#[allow(unused)]
+struct CxxFlag {
+    name: String,
+    ty: String,
+    description: String,
+    default_value: String,
+    filename: String,
+}
+
+#[derive(Debug)]
+struct SetFlagError;
+
+impl CxxFlag {
+    /// Sets the command gFlag to the specified value.
+    fn set(&self, value: &OsStr) -> Result<(), SetFlagError> {
+        Self::set_option(&self.name, value)
+    }
+
+    /// Sets the command gFlag to the specified value.
+    fn set_option(name: &str, value: &OsStr) -> Result<(), SetFlagError> {
+        unsafe {
+            let name = CString::new(name.clone()).expect("Flag name may not have NUL");
+            let value = CString::new(value.as_bytes()).expect("Arg may not have NUL");
+            if ffi::aos::SetCommandLineOption(name.as_ptr(), value.as_ptr()) {
+                Ok(())
+            } else {
+                Err(SetFlagError)
+            }
+        }
+    }
+
+    #[allow(dead_code)]
+    fn get_option(name: &str) -> String {
+        unsafe {
+            let name = CString::new(name).expect("Flag may not have NUL");
+            ffi::aos::GetCommandLineOption(name.as_ptr()).to_string()
+        }
+    }
+}
+
+impl From<&ffi::aos::FlagInfo> for CxxFlag {
+    fn from(value: &ffi::aos::FlagInfo) -> Self {
+        Self {
+            name: value.name().to_string(),
+            ty: value.ty().to_string(),
+            description: value.description().to_string(),
+            default_value: value.default_value().to_string(),
+            filename: value.filename().to_string(),
+        }
+    }
+}
+
+impl From<CxxFlag> for Arg {
+    fn from(value: CxxFlag) -> Self {
+        assert_ne!(&value.name, "help");
+        Arg::new(&value.name)
+            .long(&value.name)
+            .help(&value.description)
+            .default_value(&value.default_value)
+            .action(ArgAction::Set)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::sync::Mutex;
+
+    use super::*;
+
+    #[derive(Parser)]
+    #[command()]
+    struct App {
+        #[arg(long)]
+        myarg: u64,
+    }
+
+    // We are sharing global state through gFlags. Use a mutex to prevent races.
+    static MUTEX: Mutex<()> = Mutex::new(());
+
+    #[test]
+    fn simple_rust() {
+        let _guard = MUTEX.lock();
+        let app = App::parse_with_cpp_flags_from(&["mytest", "--myarg", "23"]);
+        assert_eq!(app.myarg, 23);
+    }
+
+    #[test]
+    fn set_cxx_flag() {
+        let _guard = MUTEX.lock();
+        let app = App::parse_with_cpp_flags_from(&[
+            "mytest",
+            "--alsologtostderr",
+            "true",
+            "--myarg",
+            "23",
+        ]);
+        assert_eq!(app.myarg, 23);
+        assert_eq!(CxxFlag::get_option("alsologtostderr"), "true");
+    }
 }
diff --git a/aos/init_for_rust.cc b/aos/init_for_rust.cc
new file mode 100644
index 0000000..9af4d83
--- /dev/null
+++ b/aos/init_for_rust.cc
@@ -0,0 +1,49 @@
+#include "aos/init_for_rust.h"
+
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+#include "aos/init.h"
+
+namespace aos {
+
+void InitFromRust(const char *argv0) {
+  CHECK(!IsInitialized()) << "Only initialize once.";
+
+  google::InitGoogleLogging(argv0);
+
+  // TODO(Brian): Where should Rust binaries be configured to write coredumps?
+
+  // TODO(Brian): Figure out what to do with allocator hooks for C++ and Rust.
+
+  MarkInitialized();
+}
+
+std::vector<FlagInfo> GetCppFlags() {
+  std::vector<gflags::CommandLineFlagInfo> info;
+  gflags::GetAllFlags(&info);
+  std::vector<FlagInfo> out;
+  for (const auto &flag : info) {
+    FlagInfo out_flag = {
+        .name_ = flag.name,
+        .type_ = flag.type,
+        .description_ = flag.description,
+        .default_value_ = flag.default_value,
+        .filename_ = flag.filename,
+    };
+    out.push_back(out_flag);
+  }
+  return out;
+}
+
+bool SetCommandLineOption(const char *name, const char *value) {
+  return !gflags::SetCommandLineOption(name, value).empty();
+}
+
+std::string GetCommandLineOption(const char *name) {
+  std::string out;
+  CHECK(gflags::GetCommandLineOption(name, &out)) << "Unknown flag " << name;
+  return out;
+}
+
+}  // namespace aos
diff --git a/aos/init_for_rust.h b/aos/init_for_rust.h
new file mode 100644
index 0000000..86bfd66
--- /dev/null
+++ b/aos/init_for_rust.h
@@ -0,0 +1,47 @@
+#ifndef AOS_INIT_FOR_RUST_H_
+#define AOS_INIT_FOR_RUST_H_
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "aos/for_rust.h"
+#include "cxx.h"
+
+namespace aos {
+
+struct FlagInfo {
+  std::string name_;
+  std::string type_;
+  std::string description_;
+  std::string default_value_;
+  std::string filename_;
+
+  rust::Str name() const {
+    return StringViewToRustStr(std::string_view(name_));
+  }
+  rust::Str ty() const { return StringViewToRustStr(std::string_view(type_)); }
+  rust::Str description() const {
+    return StringViewToRustStr(std::string_view(description_));
+  }
+  rust::Str default_value() const {
+    return StringViewToRustStr(std::string_view(default_value_));
+  }
+  rust::Str filename() const {
+    return StringViewToRustStr(std::string_view(filename_));
+  }
+};
+
+// A special initialization function that initializes the C++ parts in a way
+// compatible with Rust. This requires careful coordination with `:init_rs`, do
+// not use it from anywhere else.
+void InitFromRust(const char *argv0);
+
+std::vector<FlagInfo> GetCppFlags();
+
+bool SetCommandLineOption(const char *name, const char *value);
+std::string GetCommandLineOption(const char *name);
+
+}  // namespace aos
+
+#endif  // AOS_INIT_FOR_RUST_H_
diff --git a/aos/test_init.rs b/aos/test_init.rs
index a8380e2..d17d2ef 100644
--- a/aos/test_init.rs
+++ b/aos/test_init.rs
@@ -1,4 +1,4 @@
-use aos_init::init;
+use std::sync::Once;
 
 autocxx::include_cpp! (
 #include "aos/testing/tmpdir.h"
@@ -15,7 +15,11 @@
 ///
 /// Panics if non-test initialization has already been performed.
 pub fn test_init() {
-    init();
-    ffi::aos::testing::SetTestShmBase();
-    // TODO(Brian): Do we want any of the other stuff that `:gtest_main` has?
+    static ONCE: Once = Once::new();
+    ONCE.call_once(|| {
+        aos_init::internal::init();
+        ffi::aos::testing::SetTestShmBase();
+        env_logger::builder().is_test(true).init();
+        // TODO(Brian): Do we want any of the other stuff that `:gtest_main` has?
+    });
 }
diff --git a/aos/testing/BUILD b/aos/testing/BUILD
index 02459e4..eb9e2b2 100644
--- a/aos/testing/BUILD
+++ b/aos/testing/BUILD
@@ -1,3 +1,5 @@
+load("//tools/rust:defs.bzl", "rust_library")
+
 cc_library(
     name = "googletest",
     testonly = True,
@@ -131,3 +133,17 @@
         "@com_google_absl//absl/strings",
     ],
 )
+
+rust_library(
+    name = "aos_rs",
+    testonly = True,
+    srcs = ["aos.rs"],
+    crate_name = "aos",
+    gen_docs = False,
+    gen_doctests = False,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos:aos_rs",
+        "//aos:test_init_rs",
+    ],
+)
diff --git a/aos/testing/aos.rs b/aos/testing/aos.rs
new file mode 100644
index 0000000..6c7795d
--- /dev/null
+++ b/aos/testing/aos.rs
@@ -0,0 +1,7 @@
+// Reexport all of the [`aos`] crate
+pub use aos::*;
+
+/// Utilities for testing an AOS application.
+pub mod testing {
+    pub use aos_test_init as init;
+}
diff --git a/build_tests/BUILD b/build_tests/BUILD
index f0ff94d..282365e 100644
--- a/build_tests/BUILD
+++ b/build_tests/BUILD
@@ -4,7 +4,7 @@
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_go_library", "flatbuffer_py_library")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
 load("//tools/build_rules:apache.bzl", "apache_wrapper")
-load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
+load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
 load("//tools/build_rules:autocxx.bzl", "autocxx_library")
 
 cc_test(
@@ -153,12 +153,6 @@
     target_compatible_with = ["//tools/platforms/rust:has_support"],
 )
 
-rust_test(
-    name = "hello_lib_test",
-    crate = ":hello_lib",
-    target_compatible_with = ["//tools/platforms/rust:has_support"],
-)
-
 rust_binary(
     name = "rust_hello",
     srcs = ["rust_hello.rs"],
@@ -199,15 +193,6 @@
     target_compatible_with = ["//tools/platforms/rust:has_support"],
 )
 
-rust_test(
-    name = "hello_autocxx_test",
-    crate = ":hello_autocxx",
-    # TODO: Make Rust play happy with pic vs nopic. Details at:
-    # https://github.com/bazelbuild/rules_rust/issues/118
-    rustc_flags = ["-Crelocation-model=static"],
-    target_compatible_with = ["//tools/platforms/rust:has_support"],
-)
-
 py_test(
     name = "upstream_python_test",
     srcs = [
diff --git a/frc971/control_loops/python/constants.py b/frc971/control_loops/python/constants.py
index e4ff6b7..260a6ad 100644
--- a/frc971/control_loops/python/constants.py
+++ b/frc971/control_loops/python/constants.py
@@ -37,8 +37,7 @@
 Robot2021 = Robot2020
 Robot2022 = RobotType(width=0.8763, length=0.96647)
 Robot2023 = RobotType(width=0.6061, length=0.77581)
-#TODO (Nathan): Update 2024 robot dimensions when CAD is done
-Robot2024 = RobotType(width=0.9017, length=0.9525)  # 35.5 in x 37.5 in
+Robot2024 = RobotType(width=0.85979, length=0.9906)  # 33.85 in x 39.0 in
 
 FIELDS = {
     "2019 Field":
diff --git a/frc971/orin/argus_camera.cc b/frc971/orin/argus_camera.cc
index bb16674..a4dca06 100644
--- a/frc971/orin/argus_camera.cc
+++ b/frc971/orin/argus_camera.cc
@@ -565,6 +565,12 @@
 
       if (buffer.nvbuf_surf() == nullptr) {
         // TODO(austin): Control-C isn't working for some reason, debug it...
+        // We're restarting nvargus-daemon here because if we exit like this its
+        // likely that nvargus-daemon has run into an error that it can't
+        // recover from. Which means even if this program restarts it can't get
+        // new camera images.
+        CHECK_EQ(std::system("sudo systemctl restart nvargus-daemon.service"),
+                 0);
         event_loop.Exit();
         return;
       }
diff --git a/third_party/autocxx/gen/cmd/BUILD b/third_party/autocxx/gen/cmd/BUILD
index 60d011c..c05e24d 100644
--- a/third_party/autocxx/gen/cmd/BUILD
+++ b/third_party/autocxx/gen/cmd/BUILD
@@ -22,7 +22,7 @@
     version = "0.16.0",
     deps = [
         "//third_party/autocxx/engine:autocxx_engine",
-        "@crate_index//:clap",
+        "@crate_index//:clap3",
         "@crate_index//:env_logger",
         "@crate_index//:indexmap",
         "@crate_index//:miette",
diff --git a/third_party/autocxx/gen/cmd/Cargo.toml b/third_party/autocxx/gen/cmd/Cargo.toml
index 7a78ba3..55242ff 100644
--- a/third_party/autocxx/gen/cmd/Cargo.toml
+++ b/third_party/autocxx/gen/cmd/Cargo.toml
@@ -23,7 +23,7 @@
 
 [dependencies]
 autocxx-engine = { version = "=0.26.0", path = "../../engine" }
-clap = { version = "3.1.2", features = ["cargo"] }
+clap3 = { package = "clap", version = "3.2.25", features = ["cargo"] }
 proc-macro2 = "1.0"
 env_logger = "0.9.0"
 miette = { version = "5", features = ["fancy"] }
diff --git a/third_party/flatbuffers/rust/BUILD.bazel b/third_party/flatbuffers/rust/BUILD.bazel
index d2191dc..64a0dd8 100644
--- a/third_party/flatbuffers/rust/BUILD.bazel
+++ b/third_party/flatbuffers/rust/BUILD.bazel
@@ -9,6 +9,7 @@
     version = "2.1.1",
     visibility = ["//visibility:public"],
     deps = [
+        "@crate_index//:arrayvec",
         "@crate_index//:bitflags",
         "@crate_index//:smallvec",
         "@crate_index//:thiserror",
diff --git a/third_party/flatbuffers/rust/flatbuffers/Cargo.toml b/third_party/flatbuffers/rust/flatbuffers/Cargo.toml
index 2cba5b7..bacbcb3 100644
--- a/third_party/flatbuffers/rust/flatbuffers/Cargo.toml
+++ b/third_party/flatbuffers/rust/flatbuffers/Cargo.toml
@@ -19,6 +19,7 @@
 [dependencies]
 bitflags = "1.2.1"
 serde = { version = "1.0", optional = true }
+arrayvec = "0.7.4"
 
 [build-dependencies]
 rustc_version = "0.4.0"
diff --git a/third_party/flatbuffers/rust/flatbuffers/src/builder.rs b/third_party/flatbuffers/rust/flatbuffers/src/builder.rs
index a6e6818..d5c3e94 100644
--- a/third_party/flatbuffers/rust/flatbuffers/src/builder.rs
+++ b/third_party/flatbuffers/rust/flatbuffers/src/builder.rs
@@ -16,6 +16,7 @@
 
 #[cfg(not(feature = "std"))]
 use alloc::{vec, vec::Vec};
+use arrayvec::ArrayVec;
 use core::cmp::max;
 use core::convert::Infallible;
 use core::fmt::{Debug, Display};
@@ -131,15 +132,16 @@
     allocator: A,
     head: ReverseIndex,
 
-    field_locs: Vec<FieldLoc>,
-    written_vtable_revpos: Vec<UOffsetT>,
+    // TODO(Adam): Make vectors generic.
+    field_locs: ArrayVec<FieldLoc, 100>,
+    written_vtable_revpos: ArrayVec<UOffsetT, 20>,
 
     nested: bool,
     finished: bool,
 
     min_align: usize,
     force_defaults: bool,
-    strings_pool: Vec<WIPOffset<&'fbb str>>,
+    strings_pool: ArrayVec<WIPOffset<&'fbb str>, 20>,
 
     _phantom: PhantomData<&'fbb ()>,
 }
@@ -189,15 +191,15 @@
             allocator,
             head,
 
-            field_locs: Vec::new(),
-            written_vtable_revpos: Vec::new(),
+            field_locs: ArrayVec::new(),
+            written_vtable_revpos: ArrayVec::new(),
 
             nested: false,
             finished: false,
 
             min_align: 0,
             force_defaults: false,
-            strings_pool: Vec::new(),
+            strings_pool: ArrayVec::new(),
 
             _phantom: PhantomData,
         }
diff --git a/tools/build_rules/autocxx.bzl b/tools/build_rules/autocxx.bzl
index 6cb74de..5cadf2b 100644
--- a/tools/build_rules/autocxx.bzl
+++ b/tools/build_rules/autocxx.bzl
@@ -1,4 +1,4 @@
-load("@rules_rust//rust:defs.bzl", "rust_library")
+load("@org_frc971//tools/rust:defs.bzl", "rust_library")
 load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
 load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
 
@@ -241,7 +241,7 @@
 def autocxx_library(
         name,
         visibility = None,
-        target_compatible_with = None,
+        target_compatible_with = ["//tools/platforms/rust:has_support"],
         libs = [],
         srcs = [],
         cxxbridge_srcs = [],
@@ -251,7 +251,8 @@
         testonly = None,
         crate_features = None,
         crate_name = None,
-        gen_debug = None):
+        gen_debug = None,
+        **kwargs):
     """A macro to generate Rust <-> C++ interop code with autocxx.
 
     Creates the following rules:
@@ -343,4 +344,5 @@
         ],
         compile_data = [gen_compile_data_name],
         rustc_env_files = [gen_env_files_name],
+        **kwargs
     )
diff --git a/tools/rust/defs.bzl b/tools/rust/defs.bzl
new file mode 100644
index 0000000..88caca5
--- /dev/null
+++ b/tools/rust/defs.bzl
@@ -0,0 +1,106 @@
+load(
+    "@rules_rust//rust:defs.bzl",
+    _rust_binary = "rust_binary",
+    _rust_doc = "rust_doc",
+    _rust_doc_test = "rust_doc_test",
+    _rust_library = "rust_library",
+    _rust_test = "rust_test",
+)
+load("@com_github_google_flatbuffers//:build_defs.bzl", _flatbuffer_rust_library = "flatbuffer_rust_library")
+
+def rust_doc_test(target_compatible_with = ["//tools/platforms/rust:has_support"], tags = [], **kwargs):
+    # TODO(james): Attempting to execute this remotely results
+    # in complaints about overly large files.
+    _rust_doc_test(
+        tags = tags + ["no-remote-exec"],
+        target_compatible_with = target_compatible_with,
+        **kwargs
+    )
+
+def rust_doc(target_compatible_with = ["//tools/platforms/rust:has_support"], rustdoc_flags = ["-Dwarnings"], **kwargs):
+    _rust_doc(
+        target_compatible_with = target_compatible_with,
+        rustdoc_flags = rustdoc_flags,
+        **kwargs
+    )
+
+def rust_binary(target_compatible_with = ["//tools/platforms/rust:has_support"], rustc_flags = [], **kwargs):
+    _rust_binary(
+        target_compatible_with = select({
+            Label("//conditions:default"): target_compatible_with,
+            Label("//tools:has_msan"): ["@platforms//:incompatible"],
+        }),
+        # TODO: Make Rust play happy with pic vs nopic. Details at:
+        # https://github.com/bazelbuild/rules_rust/issues/118
+        rustc_flags = rustc_flags + ["-Crelocation-model=static"],
+        **kwargs
+    )
+
+def rust_test(target_compatible_with = ["//tools/platforms/rust:has_support"], rustc_flags = [], **kwargs):
+    _rust_test(
+        target_compatible_with = select({
+            Label("//conditions:default"): target_compatible_with,
+            Label("//tools:has_msan"): ["@platforms//:incompatible"],
+        }),
+        rustc_flags = rustc_flags + ["-Crelocation-model=static"],
+        **kwargs
+    )
+
+def rust_library(
+        name,
+        target_compatible_with = ["//tools/platforms/rust:has_support"],
+        gen_docs = True,
+        gen_tests = True,
+        gen_doctests = True,
+        **kwargs):
+    test_params = {}
+    doctest_params = {}
+    params = {}
+
+    for (param, value) in kwargs.items():
+        if param.startswith("test_"):
+            test_params[param[5:]] = value
+        elif param.startswith("doctest_"):
+            doctest_params[param[8:]] = value
+        else:
+            params[param] = value
+
+    _rust_library(
+        name = name,
+        target_compatible_with = select({
+            Label("//conditions:default"): target_compatible_with,
+            Label("//tools:has_msan"): ["@platforms//:incompatible"],
+        }),
+        **params
+    )
+
+    if gen_tests:
+        rust_test(
+            name = name + "_test",
+            crate = name,
+            **test_params
+        )
+
+    if gen_docs:
+        rust_doc(
+            name = name + "_doc",
+            crate = name,
+            target_compatible_with = ["//tools/platforms/rust:has_support"],
+            rustdoc_flags = ["--document-private-items", "-Dwarnings"],
+        )
+
+    if gen_doctests:
+        rust_doc_test(
+            name = name + "_doctest",
+            crate = name,
+            **doctest_params
+        )
+
+def flatbuffer_rust_library(target_compatible_with = ["//tools/platforms/rust:has_support"], **kwargs):
+    _flatbuffer_rust_library(
+        target_compatible_with = select({
+            Label("//conditions:default"): target_compatible_with,
+            Label("//tools:has_msan"): ["@platforms//:incompatible"],
+        }),
+        **kwargs
+    )
diff --git a/y2024/BUILD b/y2024/BUILD
index d8d0db7..4aec7d2 100644
--- a/y2024/BUILD
+++ b/y2024/BUILD
@@ -211,9 +211,12 @@
         "//frc971/zeroing:absolute_encoder",
         "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2024/control_loops/drivetrain:polydrivetrain_plants",
+        "//y2024/control_loops/superstructure/altitude:altitude_plants",
+        "//y2024/control_loops/superstructure/catapult:catapult_plants",
         "//y2024/control_loops/superstructure/climber:climber_plants",
         "//y2024/control_loops/superstructure/extend:extend_plants",
         "//y2024/control_loops/superstructure/intake_pivot:intake_pivot_plants",
+        "//y2024/control_loops/superstructure/turret:turret_plants",
         "@com_github_google_glog//:glog",
         "@com_google_absl//absl/base",
     ],
diff --git a/y2024/constants.h b/y2024/constants.h
index 254b180..3afc62c 100644
--- a/y2024/constants.h
+++ b/y2024/constants.h
@@ -11,9 +11,12 @@
 #include "frc971/zeroing/absolute_encoder.h"
 #include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2024/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+#include "y2024/control_loops/superstructure/altitude/altitude_plant.h"
+#include "y2024/control_loops/superstructure/catapult/catapult_plant.h"
 #include "y2024/control_loops/superstructure/climber/climber_plant.h"
 #include "y2024/control_loops/superstructure/extend/extend_plant.h"
 #include "y2024/control_loops/superstructure/intake_pivot/intake_pivot_plant.h"
+#include "y2024/control_loops/superstructure/turret/turret_plant.h"
 
 namespace y2024::constants {
 
@@ -91,6 +94,25 @@
   }
 
   static constexpr double kExtendEncoderCountsPerRevolution() { return 4096.0; }
+  // TODO: (niko) add the gear ratios for the intake once we have them
+  static constexpr double kCatapultEncoderCountsPerRevolution() {
+    return 4096.0;
+  }
+
+  static constexpr double kCatapultEncoderRatio() { return 12.0 / 24.0; }
+
+  static constexpr double kCatapultPotRatio() { return 12.0 / 24.0; }
+
+  static constexpr double kCatapultPotRadiansPerVolt() {
+    return kCatapultPotRatio() * (3.0 /*turns*/ / 5.0 /*volts*/) *
+           (2 * M_PI /*radians*/);
+  }
+
+  static constexpr double kMaxCatapultEncoderPulsesPerSecond() {
+    return control_loops::superstructure::catapult::kFreeSpeed / (2.0 * M_PI) *
+           control_loops::superstructure::catapult::kOutputRatio /
+           kCatapultEncoderRatio() * kCatapultEncoderCountsPerRevolution();
+  }
 
   static constexpr double kExtendEncoderRatio() { return 1.0; }
 
@@ -109,6 +131,42 @@
            kExtendEncoderRatio() * kExtendEncoderCountsPerRevolution();
   }
 
+  static constexpr double kTurretEncoderCountsPerRevolution() { return 4096.0; }
+
+  static constexpr double kTurretPotRatio() {
+    return (22.0 / 100.0) * (28.0 / 48.0) * (36.0 / 24.0);
+  }
+
+  static constexpr double kTurretEncoderRatio() { return 22.0 / 100.0; }
+
+  static constexpr double kTurretPotRadiansPerVolt() {
+    return kTurretPotRatio() * (10.0 /*turns*/ / 5.0 /*volts*/) *
+           (2 * M_PI /*radians*/);
+  }
+  static constexpr double kMaxTurretEncoderPulsesPerSecond() {
+    return control_loops::superstructure::turret::kFreeSpeed / (2.0 * M_PI) *
+           control_loops::superstructure::turret::kOutputRatio /
+           kTurretEncoderRatio() * kTurretEncoderCountsPerRevolution();
+  }
+
+  static constexpr double kAltitudeEncoderCountsPerRevolution() {
+    return 4096.0;
+  }
+
+  static constexpr double kAltitudeEncoderRatio() { return 16.0 / 162.0; }
+
+  static constexpr double kAltitudePotRatio() { return 16.0 / 162.0; }
+
+  static constexpr double kAltitudePotRadiansPerVolt() {
+    return kAltitudePotRatio() * (10.0 /*turns*/ / 5.0 /*volts*/) *
+           (2 * M_PI /*radians*/);
+  }
+  static constexpr double kMaxAltitudeEncoderPulsesPerSecond() {
+    return control_loops::superstructure::altitude::kFreeSpeed / (2.0 * M_PI) *
+           control_loops::superstructure::altitude::kOutputRatio /
+           kAltitudeEncoderRatio() * kAltitudeEncoderCountsPerRevolution();
+  }
+
   // 20 -> 28 reduction to a 0.5" radius roller
   static constexpr double kTransferRollerOutputRatio = (20.0 / 28.0) * 0.0127;
   // 20 -> 34 reduction, and the 34 is on a 0.625" radius roller
diff --git a/y2024/constants/common.json b/y2024/constants/common.json
index 4ad6cd7..e444f8d 100644
--- a/y2024/constants/common.json
+++ b/y2024/constants/common.json
@@ -55,7 +55,11 @@
     "extend_supply_current_limit": 35,
     "extend_stator_current_limit": 300,
     "extend_roller_supply_current_limit": 35,
-    "extend_roller_stator_current_limit": 60
+    "extend_roller_stator_current_limit": 60,
+    "turret_supply_current_limit": 35,
+    "turret_stator_current_limit": 60,
+    "altitude_supply_current_limit": 35,
+    "altitude_stator_current_limit": 60
   },
   "transfer_roller_voltages": {
     "transfer_in": 12.0,
diff --git a/y2024/constants/constants.fbs b/y2024/constants/constants.fbs
index 6f6e455..633dc2e 100644
--- a/y2024/constants/constants.fbs
+++ b/y2024/constants/constants.fbs
@@ -75,6 +75,10 @@
   extend_stator_current_limit:double (id: 11);
   extend_roller_supply_current_limit:double (id: 12);
   extend_roller_stator_current_limit:double (id: 13);
+  turret_supply_current_limit:double (id: 14);
+  turret_stator_current_limit:double (id: 15);
+  altitude_supply_current_limit:double (id: 16);
+  altitude_stator_current_limit:double (id: 17);
 }
 
 table TransferRollerVoltages {
diff --git a/y2024/wpilib_interface.cc b/y2024/wpilib_interface.cc
index dd8f437..bce5451 100644
--- a/y2024/wpilib_interface.cc
+++ b/y2024/wpilib_interface.cc
@@ -85,6 +85,18 @@
   return voltage * Values::kExtendPotMetersPerVolt();
 }
 
+double catapult_pot_translate(double voltage) {
+  return voltage * Values::kCatapultPotRadiansPerVolt();
+}
+
+double turret_pot_translate(double voltage) {
+  return voltage * Values::kTurretPotRadiansPerVolt();
+}
+
+double altitude_pot_translate(double voltage) {
+  return voltage * Values::kAltitudePotRadiansPerVolt();
+}
+
 double drivetrain_velocity_translate(double in) {
   return (((1.0 / in) / Values::kDrivetrainCyclesPerRevolution()) *
           (2.0 * M_PI)) *
@@ -97,7 +109,9 @@
     Values::kMaxIntakePivotEncoderPulsesPerSecond(),
     Values::kMaxClimberEncoderPulsesPerSecond(),
     Values::kMaxExtendEncoderPulsesPerSecond(),
+    Values::kMaxCatapultEncoderPulsesPerSecond(),
 });
+
 static_assert(kMaxFastEncoderPulsesPerSecond <= 1300000,
               "fast encoders are too fast");
 
@@ -159,6 +173,29 @@
                        ->extend_constants()
                        ->potentiometer_offset());
 
+      CopyPosition(catapult_encoder_, builder->add_catapult(),
+                   Values::kCatapultEncoderCountsPerRevolution(),
+                   Values::kCatapultEncoderRatio(), catapult_pot_translate,
+                   true,
+                   robot_constants_->robot()
+                       ->catapult_constants()
+                       ->potentiometer_offset());
+
+      CopyPosition(turret_encoder_, builder->add_turret(),
+                   Values::kTurretEncoderCountsPerRevolution(),
+                   Values::kTurretEncoderRatio(), turret_pot_translate, true,
+                   robot_constants_->robot()
+                       ->turret_constants()
+                       ->potentiometer_offset());
+
+      CopyPosition(altitude_encoder_, builder->add_altitude(),
+                   Values::kAltitudeEncoderCountsPerRevolution(),
+                   Values::kAltitudeEncoderRatio(), altitude_pot_translate,
+                   true,
+                   robot_constants_->robot()
+                       ->altitude_constants()
+                       ->potentiometer_offset());
+
       builder->set_transfer_beambreak(transfer_beam_break_->Get());
       builder.CheckOk(builder.Send());
     }
@@ -246,6 +283,33 @@
     extend_encoder_.set_potentiometer(::std::move(potentiometer));
   }
 
+  void set_catapult(::std::unique_ptr<frc::Encoder> encoder,
+                    ::std::unique_ptr<frc::DigitalInput> absolute_pwm,
+                    ::std::unique_ptr<frc::AnalogInput> potentiometer) {
+    fast_encoder_filter_.Add(encoder.get());
+    catapult_encoder_.set_encoder(::std::move(encoder));
+    catapult_encoder_.set_absolute_pwm(::std::move(absolute_pwm));
+    catapult_encoder_.set_potentiometer(::std::move(potentiometer));
+  }
+
+  void set_turret(::std::unique_ptr<frc::Encoder> encoder,
+                  ::std::unique_ptr<frc::DigitalInput> absolute_pwm,
+                  ::std::unique_ptr<frc::AnalogInput> potentiometer) {
+    fast_encoder_filter_.Add(encoder.get());
+    turret_encoder_.set_encoder(::std::move(encoder));
+    turret_encoder_.set_absolute_pwm(::std::move(absolute_pwm));
+    turret_encoder_.set_potentiometer(::std::move(potentiometer));
+  }
+
+  void set_altitude(::std::unique_ptr<frc::Encoder> encoder,
+                    ::std::unique_ptr<frc::DigitalInput> absolute_pwm,
+                    ::std::unique_ptr<frc::AnalogInput> potentiometer) {
+    fast_encoder_filter_.Add(encoder.get());
+    altitude_encoder_.set_encoder(::std::move(encoder));
+    altitude_encoder_.set_absolute_pwm(::std::move(absolute_pwm));
+    altitude_encoder_.set_potentiometer(::std::move(potentiometer));
+  }
+
  private:
   const Constants *robot_constants_;
 
@@ -260,8 +324,8 @@
   std::unique_ptr<frc::DigitalInput> imu_yaw_rate_input_, transfer_beam_break_;
 
   frc971::wpilib::AbsoluteEncoder intake_pivot_encoder_;
-  frc971::wpilib::AbsoluteEncoderAndPotentiometer climber_encoder_;
-  frc971::wpilib::AbsoluteEncoderAndPotentiometer extend_encoder_;
+  frc971::wpilib::AbsoluteEncoderAndPotentiometer climber_encoder_,
+      catapult_encoder_, turret_encoder_, altitude_encoder_, extend_encoder_;
 
   frc971::wpilib::DMAPulseWidthReader imu_yaw_rate_reader_;
 };
@@ -310,9 +374,16 @@
     sensor_reader.set_climber(make_encoder(5),
                               make_unique<frc::DigitalInput>(5),
                               make_unique<frc::AnalogInput>(5));
-
-    sensor_reader.set_extend(make_encoder(6), make_unique<frc::DigitalInput>(6),
-                             make_unique<frc::AnalogInput>(6));
+    sensor_reader.set_extend(make_encoder(7), make_unique<frc::DigitalInput>(7),
+                             make_unique<frc::AnalogInput>(7));
+    sensor_reader.set_catapult(make_encoder(2),
+                               make_unique<frc::DigitalInput>(2),
+                               make_unique<frc::AnalogInput>(2));
+    sensor_reader.set_turret(make_encoder(3), make_unique<frc::DigitalInput>(3),
+                             make_unique<frc::AnalogInput>(3));
+    sensor_reader.set_altitude(make_encoder(6),
+                               make_unique<frc::DigitalInput>(6),
+                               make_unique<frc::AnalogInput>(6));
 
     AddLoop(&sensor_reader_event_loop);
 
@@ -349,26 +420,34 @@
         4, false, "Drivetrain Bus", &canivore_signal_registry,
         current_limits->intake_pivot_stator_current_limit(),
         current_limits->intake_pivot_supply_current_limit());
+    std::shared_ptr<TalonFX> altitude = std::make_shared<TalonFX>(
+        5, false, "Drivetrain Bus", &canivore_signal_registry,
+        current_limits->altitude_stator_current_limit(),
+        current_limits->altitude_supply_current_limit());
+    std::shared_ptr<TalonFX> turret = std::make_shared<TalonFX>(
+        6, false, "Drivetrain Bus", &canivore_signal_registry,
+        current_limits->turret_stator_current_limit(),
+        current_limits->turret_supply_current_limit());
     std::shared_ptr<TalonFX> intake_roller = std::make_shared<TalonFX>(
-        5, false, "rio", &rio_signal_registry,
+        7, false, "rio", &rio_signal_registry,
         current_limits->intake_roller_stator_current_limit(),
         current_limits->intake_roller_supply_current_limit());
     std::shared_ptr<TalonFX> transfer_roller = std::make_shared<TalonFX>(
-        6, false, "rio", &rio_signal_registry,
+        8, false, "rio", &rio_signal_registry,
         current_limits->transfer_roller_stator_current_limit(),
         current_limits->transfer_roller_supply_current_limit());
     std::shared_ptr<TalonFX> climber = std::make_shared<TalonFX>(
-        7, false, "rio", &rio_signal_registry,
+        9, false, "rio", &rio_signal_registry,
         current_limits->climber_stator_current_limit(),
         current_limits->climber_supply_current_limit());
 
     std::shared_ptr<TalonFX> extend = std::make_shared<TalonFX>(
-        8, false, "Drivetrain Bus", &rio_signal_registry,
+        8, false, "rio", &rio_signal_registry,
         current_limits->extend_stator_current_limit(),
         current_limits->extend_supply_current_limit());
 
     std::shared_ptr<TalonFX> extend_roller = std::make_shared<TalonFX>(
-        9, false, "Drivetrain Bus", &rio_signal_registry,
+        9, false, "rio", &rio_signal_registry,
         current_limits->extend_roller_stator_current_limit(),
         current_limits->extend_roller_supply_current_limit());
 
@@ -393,7 +472,7 @@
       canivore_talonfxs.push_back(talonfx);
     }
 
-    for (auto talonfx : {intake_pivot}) {
+    for (auto talonfx : {intake_pivot, altitude, turret}) {
       canivore_talonfxs.push_back(talonfx);
     }
 
@@ -417,8 +496,8 @@
     frc971::wpilib::CANSensorReader canivore_can_sensor_reader(
         &can_sensor_reader_event_loop, std::move(canivore_signal_registry),
         canivore_talonfxs,
-        [drivetrain_talonfxs, &intake_pivot, &drivetrain_can_position_sender,
-         &superstructure_can_position_sender](
+        [drivetrain_talonfxs, &intake_pivot, &altitude, &turret,
+         &drivetrain_can_position_sender, &superstructure_can_position_sender](
             ctre::phoenix::StatusCode status) {
           aos::Sender<frc971::control_loops::drivetrain::CANPositionStatic>::
               StaticBuilder drivetrain_can_builder =
@@ -447,7 +526,13 @@
 
           intake_pivot->SerializePosition(
               superstructure_can_builder->add_intake_pivot(),
-              superstructure::intake_pivot::kOutputRatio);
+              control_loops::drivetrain::kHighOutputRatio);
+          altitude->SerializePosition(
+              superstructure_can_builder->add_altitude(),
+              control_loops::drivetrain::kHighOutputRatio);
+          turret->SerializePosition(
+              superstructure_can_builder->add_turret(),
+              control_loops::drivetrain::kHighOutputRatio);
 
           superstructure_can_builder->set_timestamp(
               intake_pivot->GetTimestamp());
@@ -519,6 +604,10 @@
                   output.extend_voltage());
               talonfx_map.find("extend_roller")
                   ->second->WriteVoltage(output.extend_roller_voltage());
+              talonfx_map.find("altitude")
+                  ->second->WriteVoltage(output.altitude_voltage());
+              talonfx_map.find("turret")->second->WriteVoltage(
+                  output.turret_voltage());
             });
 
     can_drivetrain_writer.set_talonfxs({right_front, right_back},
@@ -530,6 +619,8 @@
     can_superstructure_writer.add_talonfx("climber", climber);
     can_superstructure_writer.add_talonfx("extend", extend);
     can_superstructure_writer.add_talonfx("extend_roller", extend_roller);
+    can_superstructure_writer.add_talonfx("altitude", altitude);
+    can_superstructure_writer.add_talonfx("turret", turret);
 
     can_output_event_loop.MakeWatcher(
         "/roborio", [&can_drivetrain_writer, &can_superstructure_writer](
diff --git a/y2024/y2024_imu.json b/y2024/y2024_imu.json
index 5dd4710..f0e7561 100644
--- a/y2024/y2024_imu.json
+++ b/y2024/y2024_imu.json
@@ -208,22 +208,6 @@
       ]
     },
     {
-      "name": "localizer",
-      "executable_name": "localizer_main",
-      "user": "pi",
-      "nodes": [
-        "imu"
-      ]
-    },
-    {
-      "name": "imu",
-      "executable_name": "imu_main",
-      "user": "pi",
-      "nodes": [
-        "imu"
-      ]
-    },
-    {
       "name": "joystick_republish",
       "executable_name": "joystick_republish",
       "user": "pi",