Repository: ccbrown/iocraft (language: rust)

Issue:
Async support (`Send` compatible)?
Heya, me again.

So I'm building a console crate that I'll be using in a few project, which can be found here: https://github.com/moonrepo/starbase/pull/103

I chose iocraft for rendering output as I'm a big fan of Ink, and iocraft has been pretty great so far. However, I hit a snag in regards to async. For context, I've built a little abstraction around iocraft, so instead of rendering like this:

```
element!(...).print()
```

It would render through the console crate, like this:

```
console.out.render(element!())
```

This is necessary is the console crate buffers writes so we aren't constantly locking stdout/stdderr, so I need to ensure the current buffer is flushed correctly. I also need to wrap the elements in an internal context and pass a custom theme object down. This abstraction works great for non-async rendering (not using `render_loop`). The abstraction can be found here:  https://github.com/moonrepo/starbase/pull/103/files#diff-2b7458bb921e029ac6b237b83bfe99e97395749a37415bf2fa0193cccada4da5

However, for `render_loop`, it completely breaks down because a lot of iocraft internals are not `Send`. When rendering an element like so:

```
console.out.render_loop(element!()).await
```

Compiler errors with all of this (it's a lot):

```
error[E0277]: `Rc<std::boxed::Box<(dyn any_key::AnyHash + 'static)>>` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `Rc<std::boxed::Box<(dyn any_key::AnyHash + 'static)>>` cannot be sent between threads safely
...
228 |             |session| async move {
    |                       ---------- within this `{async block@examples/term/src/main.rs:228:23: 228:33}`
    |
    = help: within `{async block@examples/term/src/main.rs:228:23: 228:33}`, the trait `Send` is not implemented for `Rc<std::boxed::Box<(dyn any_key::AnyHash + 'static)>>`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
note: required because it appears within the type `ElementKey`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:63:12
    |
63  | pub struct ElementKey(Rc<Box<dyn AnyHash>>);
    |            ^^^^^^^^^^
note: required because it appears within the type `Element<'_, starbase_console::ui::Container>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:74:12
    |
74  | pub struct Element<'a, T: ElementType + 'a> {
    |            ^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn Any + 'static)` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn Any + 'static)` cannot be sent between threads safely
...
228 |             |session| async move {
    |                       ---------- within this `{async block@examples/term/src/main.rs:228:23: 228:33}`
    |
    = help: within `{async block@examples/term/src/main.rs:228:23: 228:33}`, the trait `Send` is not implemented for `(dyn Any + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required because it appears within the type `&mut (dyn Any + 'static)`
note: required because it appears within the type `iocraft::Context<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/context.rs:29:10
    |
29  | pub enum Context<'a> {
    |          ^^^^^^^
note: required because it appears within the type `Option<iocraft::Context<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:571:10
    |
571 | pub enum Option<T> {
    |          ^^^^^^
note: required because it appears within the type `ContextProviderProps<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/components/context_provider.rs:6:12
    |
6   | pub struct ContextProviderProps<'a> {
    |            ^^^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `Element<'_, ContextProvider>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:74:12
    |
74  | pub struct Element<'a, T: ElementType + 'a> {
    |            ^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn Any + 'static)` cannot be shared between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn Any + 'static)` cannot be shared between threads safely
    |
    = help: the trait `Sync` is not implemented for `(dyn Any + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `&(dyn Any + 'static)` to implement `Send`
note: required because it appears within the type `iocraft::Context<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/context.rs:29:10
    |
29  | pub enum Context<'a> {
    |          ^^^^^^^
note: required because it appears within the type `Option<iocraft::Context<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:571:10
    |
571 | pub enum Option<T> {
    |          ^^^^^^
note: required because it appears within the type `ContextProviderProps<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/components/context_provider.rs:6:12
    |
6   | pub struct ContextProviderProps<'a> {
    |            ^^^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `Element<'_, ContextProvider>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:74:12
    |
74  | pub struct Element<'a, T: ElementType + 'a> {
    |            ^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `*mut ()` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `*mut ()` cannot be sent between threads safely
...
228 |             |session| async move {
    |                       ---------- within this `{async block@examples/term/src/main.rs:228:23: 228:33}`
    |
    = help: within `{async block@examples/term/src/main.rs:228:23: 228:33}`, the trait `Send` is not implemented for `*mut ()`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
note: required because it appears within the type `AnyProps<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/props.rs:86:12
    |
86  | pub struct AnyProps<'a> {
    |            ^^^^^^^^
note: required because it appears within the type `AnyElement<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:92:12
    |
92  | pub struct AnyElement<'a> {
    |            ^^^^^^^^^^
note: required because it appears within the type `PhantomData<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/marker.rs:753:12
    |
753 | pub struct PhantomData<T: ?Sized>;
    |            ^^^^^^^^^^^
note: required because it appears within the type `alloc::raw_vec::RawVec<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:75:19
    |
75  | pub(crate) struct RawVec<T, A: Allocator = Global> {
    |                   ^^^^^^
note: required because it appears within the type `Vec<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:397:12
    |
397 | pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    |            ^^^
note: required because it appears within the type `ContainerProps<'_>`
   --> /Users/miles/Projects/starbase/crates/console/src/components/layout.rs:6:12
    |
6   | pub struct ContainerProps<'a> {
    |            ^^^^^^^^^^^^^^
note: required because it appears within the type `Element<'_, starbase_console::ui::Container>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:74:12
    |
74  | pub struct Element<'a, T: ElementType + 'a> {
    |            ^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn iocraft::terminal::TerminalImpl + 'static)` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn iocraft::terminal::TerminalImpl + 'static)` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `(dyn iocraft::terminal::TerminalImpl + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `Unique<(dyn iocraft::terminal::TerminalImpl + 'static)>` to implement `Send`
note: required because it appears within the type `std::boxed::Box<(dyn iocraft::terminal::TerminalImpl + 'static)>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
    |
234 | pub struct Box<
    |            ^^^
note: required because it appears within the type `iocraft::terminal::Terminal`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/terminal.rs:350:19
    |
350 | pub(crate) struct Terminal {
    |                   ^^^^^^^^
note: required because it appears within the type `ControlFlow<Result<Infallible, std::io::Error>, iocraft::terminal::Terminal>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/control_flow.rs:85:10
    |
85  | pub enum ControlFlow<B, C = ()> {
    |          ^^^^^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:361:55
    |
361 |       async fn render_loop(&mut self) -> io::Result<()> {
    |  _______________________________________________________^
362 | |         terminal_render_loop(self, Terminal::new()?).await
363 | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn ComponentHelperExt + 'static)` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn ComponentHelperExt + 'static)` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `(dyn ComponentHelperExt + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `Unique<(dyn ComponentHelperExt + 'static)>` to implement `Send`
note: required because it appears within the type `std::boxed::Box<(dyn ComponentHelperExt + 'static)>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
    |
234 | pub struct Box<
    |            ^^^
note: required because it appears within the type `AnyElement<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:92:12
    |
92  | pub struct AnyElement<'a> {
    |            ^^^^^^^^^^
note: required because it appears within the type `PhantomData<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/marker.rs:753:12
    |
753 | pub struct PhantomData<T: ?Sized>;
    |            ^^^^^^^^^^^
note: required because it appears within the type `alloc::raw_vec::RawVec<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:75:19
    |
75  | pub(crate) struct RawVec<T, A: Allocator = Global> {
    |                   ^^^^^^
note: required because it appears within the type `Vec<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:397:12
    |
397 | pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    |            ^^^
note: required because it appears within the type `ContainerProps<'_>`
   --> /Users/miles/Projects/starbase/crates/console/src/components/layout.rs:6:12
    |
6   | pub struct ContainerProps<'a> {
    |            ^^^^^^^^^^^^^^
note: required because it appears within the type `Element<'_, starbase_console::ui::Container>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:74:12
    |
74  | pub struct Element<'a, T: ElementType + 'a> {
    |            ^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `dyn iocraft::props::DropRaw` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `dyn iocraft::props::DropRaw` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `dyn iocraft::props::DropRaw`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `Unique<dyn iocraft::props::DropRaw>` to implement `Send`
note: required because it appears within the type `std::boxed::Box<dyn iocraft::props::DropRaw>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
    |
234 | pub struct Box<
    |            ^^^
note: required because it appears within the type `Option<std::boxed::Box<dyn iocraft::props::DropRaw>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:571:10
    |
571 | pub enum Option<T> {
    |          ^^^^^^
note: required because it appears within the type `AnyProps<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/props.rs:86:12
    |
86  | pub struct AnyProps<'a> {
    |            ^^^^^^^^
note: required because it appears within the type `AnyElement<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:92:12
    |
92  | pub struct AnyElement<'a> {
    |            ^^^^^^^^^^
note: required because it appears within the type `PhantomData<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/marker.rs:753:12
    |
753 | pub struct PhantomData<T: ?Sized>;
    |            ^^^^^^^^^^^
note: required because it appears within the type `alloc::raw_vec::RawVec<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:75:19
    |
75  | pub(crate) struct RawVec<T, A: Allocator = Global> {
    |                   ^^^^^^
note: required because it appears within the type `Vec<AnyElement<'_>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:397:12
    |
397 | pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    |            ^^^
note: required because it appears within the type `ContainerProps<'_>`
   --> /Users/miles/Projects/starbase/crates/console/src/components/layout.rs:6:12
    |
6   | pub struct ContainerProps<'a> {
    |            ^^^^^^^^^^^^^^
note: required because it appears within the type `Element<'_, starbase_console::ui::Container>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:74:12
    |
74  | pub struct Element<'a, T: ElementType + 'a> {
    |            ^^^^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn AnyComponent + 'static)` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn AnyComponent + 'static)` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `(dyn AnyComponent + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `Unique<(dyn AnyComponent + 'static)>` to implement `Send`
note: required because it appears within the type `std::boxed::Box<(dyn AnyComponent + 'static)>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
    |
234 | pub struct Box<
    |            ^^^
note: required because it appears within the type `iocraft::component::InstantiatedComponent`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/component.rs:131:19
    |
131 | pub(crate) struct InstantiatedComponent {
    |                   ^^^^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `iocraft::render::Tree<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:287:8
    |
287 | struct Tree<'a> {
    |        ^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:430:1
    |
430 | / {
431 | |     let h = e.helper();
432 | |     let mut tree = Tree::new(e.props_mut(), h);
433 | |     tree.terminal_render_loop(term).await
434 | | }
    | |_^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:361:55
    |
361 |       async fn render_loop(&mut self) -> io::Result<()> {
    |  _______________________________________________________^
362 | |         terminal_render_loop(self, Terminal::new()?).await
363 | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn iocraft::hook::AnyHook + 'static)` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn iocraft::hook::AnyHook + 'static)` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `(dyn iocraft::hook::AnyHook + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `Unique<(dyn iocraft::hook::AnyHook + 'static)>` to implement `Send`
note: required because it appears within the type `std::boxed::Box<(dyn iocraft::hook::AnyHook + 'static)>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
    |
234 | pub struct Box<
    |            ^^^
note: required because it appears within the type `PhantomData<std::boxed::Box<(dyn iocraft::hook::AnyHook + 'static)>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/marker.rs:753:12
    |
753 | pub struct PhantomData<T: ?Sized>;
    |            ^^^^^^^^^^^
note: required because it appears within the type `alloc::raw_vec::RawVec<std::boxed::Box<(dyn iocraft::hook::AnyHook + 'static)>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:75:19
    |
75  | pub(crate) struct RawVec<T, A: Allocator = Global> {
    |                   ^^^^^^
note: required because it appears within the type `Vec<std::boxed::Box<(dyn iocraft::hook::AnyHook + 'static)>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:397:12
    |
397 | pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    |            ^^^
note: required because it appears within the type `iocraft::component::InstantiatedComponent`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/component.rs:131:19
    |
131 | pub(crate) struct InstantiatedComponent {
    |                   ^^^^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `iocraft::render::Tree<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:287:8
    |
287 | struct Tree<'a> {
    |        ^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:430:1
    |
430 | / {
431 | |     let h = e.helper();
432 | |     let mut tree = Tree::new(e.props_mut(), h);
433 | |     tree.terminal_render_loop(term).await
434 | | }
    | |_^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:361:55
    |
361 |       async fn render_loop(&mut self) -> io::Result<()> {
    |  _______________________________________________________^
362 | |         terminal_render_loop(self, Terminal::new()?).await
363 | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `dyn Future<Output = ()>` cannot be sent between threads safely
    --> examples/term/src/main.rs:224:10
     |
224  |         .run(
     |          ^^^ `dyn Future<Output = ()>` cannot be sent between threads safely
     |
     = help: the trait `Send` is not implemented for `dyn Future<Output = ()>`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
     = note: required for `Unique<dyn Future<Output = ()>>` to implement `Send`
note: required because it appears within the type `std::boxed::Box<dyn Future<Output = ()>>`
    --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
     |
234  | pub struct Box<
     |            ^^^
note: required because it appears within the type `Pin<std::boxed::Box<dyn Future<Output = ()>>>`
    --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/pin.rs:1089:12
     |
1089 | pub struct Pin<Ptr> {
     |            ^^^
     = note: required because it appears within the type `(Pin<std::boxed::Box<dyn Future<Output = ()>>>, Pin<std::boxed::Box<dyn Future<Output = ()>>>)`
note: required because it appears within the type `Option<(Pin<std::boxed::Box<dyn Future<Output = ()>>>, Pin<std::boxed::Box<dyn Future<Output = ()>>>)>`
    --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:571:10
     |
571  | pub enum Option<T> {
     |          ^^^^^^
note: required because it appears within the type `futures_util::future::select::Select<Pin<std::boxed::Box<dyn Future<Output = ()>>>, Pin<std::boxed::Box<dyn Future<Output = ()>>>>`
    --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.31/src/future/select.rs:10:12
     |
10   | pub struct Select<A, B> {
     |            ^^^^^^
note: required because it's used within this `async` fn body
    --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:391:84
     |
391  |       async fn terminal_render_loop(&mut self, mut term: Terminal) -> io::Result<()> {
     |  ____________________________________________________________________________________^
392  | |         let mut prev_canvas: Option<Canvas> = None;
393  | |         loop {
394  | |             let width = term.width().map(|w| w as usize);
...    |
417  | |         Ok(())
418  | |     }
     | |_____^
note: required because it's used within this `async` fn body
    --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:430:1
     |
430  | / {
431  | |     let h = e.helper();
432  | |     let mut tree = Tree::new(e.props_mut(), h);
433  | |     tree.terminal_render_loop(term).await
434  | | }
     | |_^
note: required because it's used within this `async` fn body
    --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:361:55
     |
361  |       async fn render_loop(&mut self) -> io::Result<()> {
     |  _______________________________________________________^
362  | |         terminal_render_loop(self, Terminal::new()?).await
363  | |     }
     | |_____^
note: required because it's used within this `async` fn body
    --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
     |
43   |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
     |  ____________________________________________________________________________________________________^
44   | |         let theme = ConsoleTheme::default();
45   | |
46   | |         self.out.flush()?;
...    |
57   | |         Ok(())
58   | |     }
     | |_____^
note: required because it's used within this `async` fn body
    --> examples/term/src/main.rs:18:51
     |
18   |   async fn render(session: TestSession, ui: String) {
     |  ___________________________________________________^
19   | |     let con = &session.console;
20   | |
21   | |     match ui.as_str() {
...    |
211  | |     }
212  | | }
     | |_^
note: required because it's used within this `async` block
    --> examples/term/src/main.rs:228:23
     |
228  |             |session| async move {
     |                       ^^^^^^^^^^
note: required by a bound in `App::run`
    --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
     |
47   |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
     |                  --- required by a bound in this associated function
...
51   |         Fut: Future<Output = AppResult> + Send + 'static,
     |                                           ^^^^ required by this bound in `App::run`

error[E0277]: `(dyn for<'a> Fn(taffy::geometry::Size<Option<f32>>, taffy::geometry::Size<taffy::style::dimension::AvailableSpace>, &'a taffy::style::Style) -> taffy::geometry::Size<f32> + 'static)` cannot be sent between threads safely
   --> examples/term/src/main.rs:224:10
    |
224 |         .run(
    |          ^^^ `(dyn for<'a> Fn(taffy::geometry::Size<Option<f32>>, taffy::geometry::Size<taffy::style::dimension::AvailableSpace>, &'a taffy::style::Style) -> taffy::geometry::Size<f32> + 'static)` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `(dyn for<'a> Fn(taffy::geometry::Size<Option<f32>>, taffy::geometry::Size<taffy::style::dimension::AvailableSpace>, &'a taffy::style::Style) -> taffy::geometry::Size<f32> + 'static)`, which is required by `{async block@examples/term/src/main.rs:228:23: 228:33}: Send`
    = note: required for `Unique<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32>>` to implement `Send`
note: required because it appears within the type `Box<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
    |
234 | pub struct Box<
    |            ^^^
note: required because it appears within the type `Option<Box<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32>>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:571:10
    |
571 | pub enum Option<T> {
    |          ^^^^^^
note: required because it appears within the type `iocraft::render::LayoutEngineNodeContext`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:281:19
    |
281 | pub(crate) struct LayoutEngineNodeContext {
    |                   ^^^^^^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `slotmap::sparse_secondary::Slot<iocraft::render::LayoutEngineNodeContext>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/slotmap-1.0.7/src/sparse_secondary.rs:17:8
    |
17  | struct Slot<T> {
    |        ^^^^
    = note: required because it appears within the type `(u32, slotmap::sparse_secondary::Slot<iocraft::render::LayoutEngineNodeContext>)`
    = note: required for `hashbrown::raw::RawTable<(u32, slotmap::sparse_secondary::Slot<iocraft::render::LayoutEngineNodeContext>)>` to implement `Send`
note: required because it appears within the type `hashbrown::map::HashMap<u32, slotmap::sparse_secondary::Slot<iocraft::render::LayoutEngineNodeContext>, RandomState>`
   --> /rust/deps/hashbrown-0.15.0/src/map.rs:185:12
note: required because it appears within the type `HashMap<u32, slotmap::sparse_secondary::Slot<iocraft::render::LayoutEngineNodeContext>>`
   --> /Users/miles/.rustup/toolchains/1.83.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/collections/hash/map.rs:211:12
    |
211 | pub struct HashMap<K, V, S = RandomState> {
    |            ^^^^^^^
note: required because it appears within the type `slotmap::sparse_secondary::SparseSecondaryMap<slotmap::DefaultKey, iocraft::render::LayoutEngineNodeContext>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/slotmap-1.0.7/src/sparse_secondary.rs:71:12
    |
71  | pub struct SparseSecondaryMap<K: Key, V, S: hash::BuildHasher = hash_map::RandomState> {
    |            ^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `taffy::tree::taffy_tree::TaffyTree<iocraft::render::LayoutEngineNodeContext>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/taffy-0.5.2/src/tree/taffy_tree.rs:127:12
    |
127 | pub struct TaffyTree<NodeContext = ()> {
    |            ^^^^^^^^^
note: required because it appears within the type `iocraft::render::Tree<'_>`
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:287:8
    |
287 | struct Tree<'a> {
    |        ^^^^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/render.rs:430:1
    |
430 | / {
431 | |     let h = e.helper();
432 | |     let mut tree = Tree::new(e.props_mut(), h);
433 | |     tree.terminal_render_loop(term).await
434 | | }
    | |_^
note: required because it's used within this `async` fn body
   --> /Users/miles/.cargo/registry/src/index.crates.io-6f17d22bba15001f/iocraft-0.4.1/src/element.rs:361:55
    |
361 |       async fn render_loop(&mut self) -> io::Result<()> {
    |  _______________________________________________________^
362 | |         terminal_render_loop(self, Terminal::new()?).await
363 | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> /Users/miles/Projects/starbase/crates/console/src/ui.rs:43:100
    |
43  |       pub async fn render_loop_1<T: Component>(&self, element: Element<'_, T>) -> miette::Result<()> {
    |  ____________________________________________________________________________________________________^
44  | |         let theme = ConsoleTheme::default();
45  | |
46  | |         self.out.flush()?;
...   |
57  | |         Ok(())
58  | |     }
    | |_____^
note: required because it's used within this `async` fn body
   --> examples/term/src/main.rs:18:51
    |
18  |   async fn render(session: TestSession, ui: String) {
    |  ___________________________________________________^
19  | |     let con = &session.console;
20  | |
21  | |     match ui.as_str() {
...   |
211 | |     }
212 | | }
    | |_^
note: required because it's used within this `async` block
   --> examples/term/src/main.rs:228:23
    |
228 |             |session| async move {
    |                       ^^^^^^^^^^
note: required by a bound in `App::run`
   --> /Users/miles/Projects/starbase/crates/app/src/app.rs:51:43
    |
47  |     pub async fn run<S, F, Fut>(self, mut session: S, op: F) -> miette::Result<u8>
    |                  --- required by a bound in this associated function
...
51  |         Fut: Future<Output = AppResult> + Send + 'static,
    |                                           ^^^^ required by this bound in `App::run`
    = note: the full name for the type has been written to '/Users/miles/Projects/starbase/target/debug/deps/example_term-db0bd3f6265d12ed.long-type-3962756495123664969.txt'
    = note: consider using `--verbose` to print the full type name to the console
    = note: the full name for the type has been written to '/Users/miles/Projects/starbase/target/debug/deps/example_term-db0bd3f6265d12ed.long-type-5395046870147651821.txt'
    = note: consider using `--verbose` to print the full type name to the console
    = note: the full name for the type has been written to '/Users/miles/Projects/starbase/target/debug/deps/example_term-db0bd3f6265d12ed.long-type-7018701843252465699.txt'
    = note: consider using `--verbose` to print the full type name to the console

For more information about this error, try `rustc --explain E0277`.
error: could not compile `example_term` (bin "example_term") due to 11 previous errors
```

Has there been any thoughts on this? Or should I ditch my abstraction.


Discussion / hints:
Let me make my way through this trace and see if I can make this work! I'll report back in a few hours.

Fix patch:
diff --git a/packages/iocraft-macros/src/lib.rs b/packages/iocraft-macros/src/lib.rs
index 3123b0e..da25303 100644
--- a/packages/iocraft-macros/src/lib.rs
+++ b/packages/iocraft-macros/src/lib.rs
@@ -531,12 +531,12 @@ impl ToTokens for ParsedComponent {
 /// ```
 /// # use iocraft::prelude::*;
 /// #[derive(Default, Props)]
-/// struct MyGenericComponentProps<T> {
+/// struct MyGenericComponentProps<T: Send + Sync> {
 ///     items: Vec<T>,
 /// }
 ///
 /// #[component]
-/// fn MyGenericComponent<T: 'static>(
+/// fn MyGenericComponent<T: Send + Sync + 'static>(
 ///     _props: &MyGenericComponentProps<T>,
 /// ) -> impl Into<AnyElement<'static>> {
 ///     element!(Box)
diff --git a/packages/iocraft/src/component.rs b/packages/iocraft/src/component.rs
index 08a3934..3790614 100644
--- a/packages/iocraft/src/component.rs
+++ b/packages/iocraft/src/component.rs
@@ -28,7 +28,7 @@ impl<C: Component> ComponentHelper<C> {
 }
 
 #[doc(hidden)]
-pub trait ComponentHelperExt: Any {
+pub trait ComponentHelperExt: Any + Send + Sync {
     fn new_component(&self, props: AnyProps) -> Box<dyn AnyComponent>;
     fn update_component(
         &self,
@@ -70,7 +70,7 @@ impl<C: Component> ComponentHelperExt for ComponentHelper<C> {
 ///
 /// Most users will not need to implement this trait directly. This is only required for new, low
 /// level component type definitions. Instead, the [`component`](macro@crate::component) macro should be used.
-pub trait Component: Any + Unpin {
+pub trait Component: Any + Send + Sync + Unpin {
     /// The type of properties that the component accepts.
     type Props<'a>: Props
     where
@@ -103,7 +103,7 @@ impl<C: Component> ElementType for C {
 }
 
 #[doc(hidden)]
-pub trait AnyComponent: Any + Unpin {
+pub trait AnyComponent: Any + Send + Sync + Unpin {
     fn update(&mut self, props: AnyProps, hooks: Hooks, updater: &mut ComponentUpdater);
     fn draw(&mut self, drawer: &mut ComponentDrawer<'_>);
     fn poll_change(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()>;
diff --git a/packages/iocraft/src/context.rs b/packages/iocraft/src/context.rs
index 3860921..f211095 100644
--- a/packages/iocraft/src/context.rs
+++ b/packages/iocraft/src/context.rs
@@ -29,31 +29,31 @@ impl SystemContext {
 pub enum Context<'a> {
     /// Provides the context via a mutable reference. Children will be able to get mutable or
     /// immutable references to the context.
-    Mut(&'a mut dyn Any),
+    Mut(&'a mut (dyn Any + Send + Sync)),
     /// Provides the context via an immutable reference. Children will not be able to get a mutable
     /// reference to the context.
-    Ref(&'a dyn Any),
+    Ref(&'a (dyn Any + Send + Sync)),
     /// Provides the context via an owned value. Children will be able to get mutable or immutable
     /// references to the context.
-    Owned(Box<dyn Any>),
+    Owned(Box<(dyn Any + Send + Sync)>),
 }
 
 impl<'a> Context<'a> {
     /// Creates a new context from an owned value. Children will be able to get mutable or
     /// immutable references to the context.
-    pub fn owned<T: Any>(context: T) -> Self {
+    pub fn owned<T: Any + Send + Sync>(context: T) -> Self {
         Context::Owned(Box::new(context))
     }
 
     /// Creates a new context from a mutable reference. Children will be able to get mutable or
     /// immutable references to the context.
-    pub fn from_mut<T: Any>(context: &'a mut T) -> Self {
+    pub fn from_mut<T: Any + Send + Sync>(context: &'a mut T) -> Self {
         Context::Mut(context)
     }
 
     /// Creates a new context from an immutable reference. Children will not be able to get a
     /// mutable reference to the context.
-    pub fn from_ref<T: Any>(context: &'a T) -> Self {
+    pub fn from_ref<T: Any + Send + Sync>(context: &'a T) -> Self {
         Context::Ref(context)
     }
 
@@ -91,7 +91,7 @@ pub struct ContextStack<'a> {
 }
 
 impl<'a> ContextStack<'a> {
-    pub(crate) fn root(root_context: &'a mut dyn Any) -> Self {
+    pub(crate) fn root(root_context: &'a mut (dyn Any + Send + Sync)) -> Self {
         Self {
             contexts: vec![RefCell::new(Context::Mut(root_context))],
         }
diff --git a/packages/iocraft/src/element.rs b/packages/iocraft/src/element.rs
index a0229d7..6e83b1e 100644
--- a/packages/iocraft/src/element.rs
+++ b/packages/iocraft/src/element.rs
@@ -12,7 +12,7 @@ use std::{
     future::Future,
     hash::Hash,
     io::{self, stderr, stdout, IsTerminal, Write},
-    rc::Rc,
+    sync::Arc,
 };
 
 /// Used by the `element!` macro to extend a collection with elements.
@@ -60,12 +60,12 @@ where
 /// Used to identify an element within the scope of its parent. This is used to minimize the number
 /// of times components are destroyed and recreated from render-to-render.
 #[derive(Clone, Hash, PartialEq, Eq, Debug)]
-pub struct ElementKey(Rc<Box<dyn AnyHash>>);
+pub struct ElementKey(Arc<Box<dyn AnyHash + Send + Sync>>);
 
 impl ElementKey {
     /// Constructs a new key.
-    pub fn new<K: Debug + Hash + Eq + 'static>(key: K) -> Self {
-        Self(Rc::new(Box::new(key)))
+    pub fn new<K: Debug + Hash + Eq + Send + Sync + 'static>(key: K) -> Self {
+        Self(Arc::new(Box::new(key)))
     }
 }
 
diff --git a/packages/iocraft/src/handler.rs b/packages/iocraft/src/handler.rs
index 7554ffb..40fa677 100644
--- a/packages/iocraft/src/handler.rs
+++ b/packages/iocraft/src/handler.rs
@@ -7,7 +7,7 @@ use std::{
 ///
 /// Any function that takes a single argument and returns `()` can be converted into a `Handler`,
 /// and it can be invoked using function call syntax.
-pub struct Handler<'a, T>(bool, Box<dyn FnMut(T) + Send + 'a>);
+pub struct Handler<'a, T>(bool, Box<dyn FnMut(T) + Send + Sync + 'a>);
 
 impl<'a, T> Handler<'a, T> {
     /// Returns `true` if the handler was default-initialized.
@@ -29,7 +29,7 @@ impl<'a, T> Default for Handler<'a, T> {
 
 impl<'a, T, F> From<F> for Handler<'a, T>
 where
-    F: FnMut(T) + Send + 'a,
+    F: FnMut(T) + Send + Sync + 'a,
 {
     fn from(f: F) -> Self {
         Self(true, Box::new(f))
@@ -37,7 +37,7 @@ where
 }
 
 impl<'a, T: 'a> Deref for Handler<'a, T> {
-    type Target = dyn FnMut(T) + Send + 'a;
+    type Target = dyn FnMut(T) + Send + Sync + 'a;
 
     fn deref(&self) -> &Self::Target {
         &self.1
diff --git a/packages/iocraft/src/hook.rs b/packages/iocraft/src/hook.rs
index 77fa437..266ab9c 100644
--- a/packages/iocraft/src/hook.rs
+++ b/packages/iocraft/src/hook.rs
@@ -10,7 +10,7 @@ use std::{
 ///
 /// Hooks are created by implementing this trait. All methods have default implementations, so
 /// you only need to implement the ones you care about.
-pub trait Hook: Unpin {
+pub trait Hook: Unpin + Send {
     /// Called to determine if the hook has caused a change which requires its component to be
     /// redrawn.
     fn poll_change(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<()> {
diff --git a/packages/iocraft/src/hooks/use_async_handler.rs b/packages/iocraft/src/hooks/use_async_handler.rs
index b1cefb3..771c14f 100644
--- a/packages/iocraft/src/hooks/use_async_handler.rs
+++ b/packages/iocraft/src/hooks/use_async_handler.rs
@@ -21,14 +21,14 @@ pub trait UseAsyncHandler: private::Sealed {
     /// resulting future to completion.
     fn use_async_handler<T, Fun, Fut>(&mut self, f: Fun) -> Handler<'static, T>
     where
-        Fun: FnMut(T) -> Fut + Send + 'static,
+        Fun: FnMut(T) -> Fut + Send + Sync + 'static,
         Fut: Future<Output = ()> + Send + 'static;
 }
 
 impl UseAsyncHandler for Hooks<'_, '_> {
     fn use_async_handler<T, Fun, Fut>(&mut self, mut f: Fun) -> Handler<'static, T>
     where
-        Fun: FnMut(T) -> Fut + Send + 'static,
+        Fun: FnMut(T) -> Fut + Send + Sync + 'static,
         Fut: Future<Output = ()> + Send + 'static,
     {
         let handler_impl_state = self.use_hook(UseAsyncHandlerImpl::default).state.clone();
diff --git a/packages/iocraft/src/props.rs b/packages/iocraft/src/props.rs
index 935d1e3..72d3a48 100644
--- a/packages/iocraft/src/props.rs
+++ b/packages/iocraft/src/props.rs
@@ -60,7 +60,7 @@ use std::marker::PhantomData;
 /// implemented for a type that is not actually covariant, then the safety of the program is
 /// compromised. You can use the [`#[derive(Props)]`](derive@crate::Props) macro to implement this trait safely. If the
 /// type is not actually covariant, the derive macro will give you an error at compile-time.
-pub unsafe trait Props {}
+pub unsafe trait Props: Send + Sync {}
 
 #[doc(hidden)]
 #[derive(Clone, Copy, iocraft_macros::Props, Default)]
@@ -89,6 +89,12 @@ pub struct AnyProps<'a> {
     _marker: PhantomData<&'a mut ()>,
 }
 
+// SAFETY: Safe because `Props` must be `Send` and `Sync`.
+unsafe impl Send for AnyProps<'_> {}
+
+// SAFETY: Safe because `Props` must be `Sync`.
+unsafe impl Sync for AnyProps<'_> {}
+
 impl<'a> AnyProps<'a> {
     pub(crate) fn owned<T: Props + 'a>(props: T) -> Self {
         let raw = Box::into_raw(Box::new(props));
diff --git a/packages/iocraft/src/render.rs b/packages/iocraft/src/render.rs
index eb7322f..9546129 100644
--- a/packages/iocraft/src/render.rs
+++ b/packages/iocraft/src/render.rs
@@ -275,7 +275,7 @@ impl<'a> ComponentDrawer<'a> {
     }
 }
 
-type MeasureFunc = Box<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32>>;
+type MeasureFunc = Box<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32> + Send>;
 
 #[derive(Default)]
 pub(crate) struct LayoutEngineNodeContext {
@@ -405,11 +405,7 @@ impl<'a> Tree<'a> {
             if self.system_context.should_exit() || term.received_ctrl_c() {
                 break;
             }
-            select(
-                self.root_component.wait().boxed_local(),
-                term.wait().boxed_local(),
-            )
-            .await;
+            select(self.root_component.wait().boxed(), term.wait().boxed()).await;
             if term.received_ctrl_c() {
                 break;
             }
@@ -470,10 +466,11 @@ where
 
 #[cfg(test)]
 mod tests {
+    use super::*;
     use crate::prelude::*;
-    use futures::stream::StreamExt;
     use macro_rules_attribute::apply;
     use smol_macros::test;
+    use std::future::Future;
 
     #[derive(Default, Props)]
     struct MyInnerComponentProps {
@@ -531,6 +528,17 @@ mod tests {
         assert_eq!(actual, expected);
     }
 
+    async fn await_send_future<F: Future<Output = io::Result<()>> + Send>(f: F) {
+        f.await.unwrap();
+    }
+
+    // Make sure terminal_render_loop can be sent across threads.
+    #[apply(test!)]
+    async fn test_terminal_render_loop_send() {
+        let (term, _output) = Terminal::mock(MockTerminalConfig::default());
+        await_send_future(terminal_render_loop(element!(MyComponent), term)).await;
+    }
+
     #[component]
     fn FullWidthComponent() -> impl Into<AnyElement<'static>> {
         element! {
diff --git a/packages/iocraft/src/terminal.rs b/packages/iocraft/src/terminal.rs
index bff4a5d..0637d30 100644
--- a/packages/iocraft/src/terminal.rs
+++ b/packages/iocraft/src/terminal.rs
@@ -111,7 +111,7 @@ impl Stream for TerminalEvents {
     }
 }
 
-trait TerminalImpl: Write {
+trait TerminalImpl: Write + Send {
     fn width(&self) -> Option<u16>;
     fn is_raw_mode_enabled(&self) -> bool;
     fn clear_canvas(&mut self) -> io::Result<()>;


Test patch:
diff --git a/packages/iocraft-macros/tests/component.rs b/packages/iocraft-macros/tests/component.rs
index d3a9778..7de4899 100644
--- a/packages/iocraft-macros/tests/component.rs
+++ b/packages/iocraft-macros/tests/component.rs
@@ -29,20 +29,20 @@ fn MyComponentWithHooksRef(_hooks: &mut Hooks) -> impl Into<AnyElement<'static>>
 }
 
 #[derive(Props)]
-struct MyGenericProps<T, const U: usize> {
+struct MyGenericProps<T: Send + Sync, const U: usize> {
     foo: [T; U],
 }
 
 #[component]
-fn MyComponentWithGenericProps<T: 'static, const U: usize>(
+fn MyComponentWithGenericProps<T: Send + Sync + 'static, const U: usize>(
     _props: &mut MyGenericProps<T, U>,
 ) -> impl Into<AnyElement<'static>> {
     element!(Box)
 }
 
-fn check_component_traits<T: Sync + Send>() {}
+fn check_component_traits<T: Send + Sync>() {}
 
-fn check_component_traits_with_generic<T: 'static, const U: usize>() {
+fn check_component_traits_with_generic<T: Send + Sync + 'static, const U: usize>() {
     check_component_traits::<MyComponentWithGenericProps<T, U>>();
 }
 
@@ -51,7 +51,7 @@ fn MyComponentWithGenericPropsWhereClause<T, const U: usize>(
     _props: &mut MyGenericProps<T, U>,
 ) -> impl Into<AnyElement<'static>>
 where
-    T: 'static,
+    T: Send + Sync + 'static,
 {
     element!(Box)
 }
diff --git a/packages/iocraft-macros/tests/element.rs b/packages/iocraft-macros/tests/element.rs
index 3896c71..0d47b6d 100644
--- a/packages/iocraft-macros/tests/element.rs
+++ b/packages/iocraft-macros/tests/element.rs
@@ -35,16 +35,16 @@ impl Component for MyContainer {
     }
 }
 
-struct MyGenericComponent<T> {
-    _marker: std::marker::PhantomData<*const T>,
+struct MyGenericComponent<T: Sync + 'static> {
+    _marker: std::marker::PhantomData<&'static T>,
 }
 
 #[derive(Default, Props)]
-struct MyGenericComponentProps<T> {
+struct MyGenericComponentProps<T: Send + Sync> {
     items: Vec<T>,
 }
 
-impl<T: 'static> Component for MyGenericComponent<T> {
+impl<T: Send + Sync + 'static> Component for MyGenericComponent<T> {
     type Props<'a> = MyGenericComponentProps<T>;
 
     fn new(_props: &Self::Props<'_>) -> Self {
diff --git a/packages/iocraft-macros/tests/props.rs b/packages/iocraft-macros/tests/props.rs
index 2a72f62..04aa2f9 100644
--- a/packages/iocraft-macros/tests/props.rs
+++ b/packages/iocraft-macros/tests/props.rs
@@ -25,11 +25,11 @@ struct StructWithLifetimeAndConsts<'lt, const N: usize, const M: usize> {
 }
 
 #[derive(Props)]
-struct StructWithTypeGeneric<T> {
+struct StructWithTypeGeneric<T: Send + Sync> {
     foo: T,
 }
 
 #[derive(Props)]
-struct StructWithLifetimeAndTypeGeneric<'lt, T> {
+struct StructWithLifetimeAndTypeGeneric<'lt, T: Sync> {
     foo: &'lt T,
 }
diff --git a/packages/iocraft-macros/tests/with_layout_style_props.rs b/packages/iocraft-macros/tests/with_layout_style_props.rs
index d24105d..2f26fa2 100644
--- a/packages/iocraft-macros/tests/with_layout_style_props.rs
+++ b/packages/iocraft-macros/tests/with_layout_style_props.rs
@@ -15,7 +15,7 @@ struct MyPropsWithLifetime<'lt> {
 
 #[with_layout_style_props]
 #[derive(Default, Props)]
-struct MyPropsWithTypeGeneric<T> {
+struct MyPropsWithTypeGeneric<T: Send + Sync> {
     foo: Option<T>,
 }