#2767·ratatui

`portable-atomic` feature on `ratatui` does not forward to `ratatui-widgets`, breaking `no_std` builds on targets without atomic CAS

Author: Vaishnav-Sabari-GirishCreated Sep 10, 2026Updated Sep 10, 2026
LabelsType: Bug

Description

When building ratatui for a target that lacks native atomic compare-and-swap (e.g. thumbv6m-none-eabi, the Cortex-M0+ core used on the RP2040), the portable-atomic feature on the top-level ratatui crate is insufficient. It forwards to ratatui-core, but not to ratatui-widgets, which still unconditionally imports alloc::sync::Arc in src/block/shadow.rs.

The alloc::sync module is gated behind #[cfg(target_has_atomic = "ptr")], so on these targets the import fails at compile time with:

error[E0432]: unresolved import `alloc::sync`
   --> ratatui-widgets-0.3.2/src/block/shadow.rs:1:12
    |
  1 | use alloc::sync::Arc;
    |            ^^^^ could not find `sync` in `alloc`

Even adding portable-atomic to ratatui's feature list (as suggested by the ratatui book's embedded chapter) does not resolve this, because the feature is not forwarded to ratatui-widgets.

To Reproduce

Minimal Cargo.toml:

[package]
name = "repro"
version = "0.1.0"
edition = "2021"

[dependencies]
portable-atomic = { version = "1.15", features = ["critical-section"] }
ratatui = { version = "0.30.2", default-features = false, features = ["portable-atomic"] }

src/main.rs:

#![no_std]
#![no_main]

use ratatui::widgets::Block;

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { loop {} }

Build:

rustup target add thumbv6m-none-eabi
cargo build --target thumbv6m-none-eabi

Observe the unresolved import alloc::sync error from ratatui-widgets.

Expected behavior

Either:

  1. The portable-atomic feature on ratatui forwards to ratatui-widgets, and ratatui-widgets conditionally imports Arc from portable_atomic_util when that feature is enabled. This matches how ratatui-core already handles the situation, and how tracing-core solved the same problem (portable-atomic feature switches Arc/Weak imports away from alloc::sync).

  2. Or ratatui-widgets exposes its own portable-atomic feature, and ratatui's feature forwards to it. This is the least intrusive change but requires two feature declarations.

Option 1 is preferable because it keeps the feature name consistent across the workspace.

Screenshots

N/A

Are you willing to contribute a fix?

  • I am willing to open a PR for this bug.
  • I can try to investigate, but I will need guidance.
  • I am not able to work on a fix right now.

Environment

  • OS: Linux (x86_64 host, thumbv6m-none-eabi target)
  • Terminal Emulator: N/A (embedded)
  • Font: N/A
  • Crate version: ratatui 0.30.2, ratatui-widgets 0.3.2, ratatui-core 0.1.2
  • Backend: mousefood 0.5.2 (embedded-graphics backend)

Additional context

  • portable-atomic is already a transitive dependency, pulled in by both ratatui-core and kasuari (through ratatui-widgets). The latter also has a portable-atomic feature that is not forwarded from ratatui.
  • The Arc<dyn CellEffect> field in Shadow complicates a straightforward swap to portable_atomic_util::Arc, because that type cannot implement CoerceUnsized on stable Rust. My local workaround replaced the field with Box<dyn CellEffect> and added a CellEffectClone supertrait to preserve Clone on Shadow (which Block derives). A cleaner upstream fix might use Arc<dyn CellEffect> with portable_atomic_util on no_std targets, at the cost of requiring nightly for the CoerceUnsized impl. Alternatively, CellEffect could be given a clone_box method and Effect::Custom could store Box<dyn CellEffect> unconditionally, removing the Arc requirement entirely.
  • Related discussion: the Ratatui book's "Embedded" chapter suggests ratatui = { features = ["portable-atomic"] } as the fix, but this is not sufficient in practice for the reason above.