In questa sezione

Le differenze tra capdag (Rust) e capdag-objc (Swift/Objective-C) e come scrivere cartucce in ciascuno dei due.

Copertura dei moduli

Moduli condivisi (entrambi i linguaggi)

Entrambe le implementazioni coprono le stesse funzionalità di base:

Modulo Posizione in Rust Posizione in Swift
Tipi URN (CapUrn, MediaUrn, CapMatrix) capdag-rs/src/urn/ capdag-objc/Sources/CapDAG/
Definizioni e validazione delle cap capdag-rs/src/cap/ capdag-objc/Sources/CapDAG/
Protocollo CapDAG (Frame, IO) capdag-rs/src/bifaci/frame.rs, io.rs capdag-objc/Sources/Bifaci/
CartridgeRuntime capdag-rs/src/bifaci/cartridge_runtime.rs capdag-objc/Sources/Bifaci/CartridgeRuntime.swift
CartridgeHost capdag-rs/src/bifaci/host_runtime.rs capdag-objc/Sources/Bifaci/CartridgeHost.swift
Relay e RelaySwitch capdag-rs/src/bifaci/relay.rs, relay_switch.rs capdag-objc/Sources/Bifaci/Relay.swift, RelaySwitch.swift
Planner: modello del piano, grafo delle cap e ricerca dei percorsi, cardinalità, binding degli argomenti e requisiti, mappatura dell’avanzamento capdag-rs/src/planner/ capdag-objc/Sources/CapDAG/Planner/
Orchestratore: tipi del grafo risolto e convertitore di piani capdag-rs/src/orchestrator/types.rs, plan_converter.rs capdag-objc/Sources/Bifaci/Orchestrator/
Host in-process capdag-rs/src/bifaci/in_process_host.rs capdag-objc/Sources/Bifaci/InProcessCartridgeHost.swift

Solo Rust

  • Executor dell’orchestratore (capdag-rs/src/orchestrator/executor.rs, execute_plan.rs): esegue un piano — avvio dei processi, gestione dei socket Unix, ciclo di vita dei relay. È profondamente dipendente dalla piattaforma e non è stato portato.
  • Notazione delle macchine (capdag-rs/src/machine/): la grammatica (machine.pest), il parser, il resolver e il serializzatore. Anche Go, Python e JavaScript la hanno; Swift legge piani, non notazione.
  • Ricerca del planner e costruzione dei piani (capdag-rs/src/planner/plan_engine.rs, plan_builder.rs): Swift ha il grafo delle cap e la sua ricerca dei percorsi. Costruire un piano, e analizzare che cosa chiede un’esecuzione di esso, sono solo in Rust.
  • Risolutore degli input (capdag-rs/src/input_resolver/): rilevamento del tipo di file e adattatori di media.

Solo Swift

  • Livello di base Objective-C (capdag-objc/Sources/CapDAG/): CSCapUrn, CSCap, ecc.: API compatibile con C per l’interoperabilità con il codice Objective-C e con la shell dell’app macOS.

Pattern specifici del linguaggio

Runtime asincrono

Aspetto Rust Swift
Runtime tokio multi-thread concorrenza strutturata (async/await)
Punto di ingresso #[tokio::main] async fn main() @main struct, main() sincrono
Avvio di task tokio::spawn(async { ... }) Task { ... }
Lavoro bloccante tokio::task::spawn_blocking(closure) Task in background + annullamento
Canali tokio::sync::mpsc AsyncStream, canali di Foundation

La differenza architetturale fondamentale: spawn_blocking di Rust sposta il lavoro su un pool di thread dedicato, liberando esplicitamente i worker asincroni. La concorrenza strutturata di Swift non ha una separazione equivalente, quindi il lavoro bloccante all’interno di un Task può bloccare il pool di thread cooperativo. È per questo che il keepalive di Swift usa un Task in background separato invece di un passaggio a un pool di thread.

Gestione degli errori

Aspetto Rust Swift
Pattern Result<T, E> con l’operatore ? throws / try / catch
Tipi di errore macro derive di thiserror Enum conformi a Error + LocalizedError
Interoperabilità — NSError ai confini con Objective-C

Entrambe le implementazioni definiscono varianti di errore corrispondenti (ad esempio RuntimeError::NoHandler ↔ CartridgeRuntimeError.noHandler). La semantica degli errori è identica: le stesse condizioni producono gli stessi errori.

Registrazione delle op

Rust: closure factory che restituisce un trait object:

runtime.register_op("cap:...", || Box::new(MyOp::default()));
// oppure
runtime.register_op_type::<MyOp>("cap:...");

Swift: closure che restituisce un tipo conforme a Op:

runtime.register_op_type(capUrn: "cap:...", make: MyOp.init)

Provenienza dei cap URN

Rust: i cap URN possono essere estratti dal manifest a runtime tramite una ricerca con iteratore, costruiti con CapUrnBuilder oppure ottenuti da helper standard come generate_thumbnail_urn().

Swift: i cap URN sono tipicamente costanti stringa scritte nel codice. È meno dinamico ma più semplice per le cartucce con un insieme di cap fisso.

Primitive di concorrenza

Concetto Rust Swift
Condivisione thread-safe Arc<T> + trait Send + Sync protocollo Sendable, @unchecked Sendable
Mutabilità Arc<Mutex<T>>, AtomicBool NSLock, actor, OSAllocatedUnfairLock
Canali asincroni tokio::sync::mpsc, tokio::sync::oneshot AsyncStream, canali di Foundation
Thread worker tokio::task::spawn_blocking Nessun equivalente diretto (usa Task o GCD)

Differenze nel keepalive

graph TD
    subgraph Rust
        R_CALL["run_with_keepalive()"] --> R_SB["spawn_blocking<br/>(pool di thread bloccanti)"]
        R_CALL --> R_SEL["tokio::select!<br/>interval(30s)"]
        R_SEL -->|ogni 30s| R_PROG["Frame di avanzamento"]
        R_SB -->|completato| R_DONE["select! termina"]
    end

    subgraph Swift
        S_CALL["runWithKeepalive()"] --> S_TASK["Task in background<br/>loop { sleep(30s) }"]
        S_CALL --> S_OP["Operazione asincrona"]
        S_TASK -->|ogni 30s| S_PROG["Frame di avanzamento"]
        S_OP -->|completata| S_CANCEL["Annulla il Task in background"]
    end

Entrambe le implementazioni emettono frame di keepalive ogni 30 secondi durante le operazioni bloccanti. Il meccanismo è diverso:

Rust (run_with_keepalive):

  1. La closure gira su tokio::task::spawn_blocking (pool di thread dedicato al lavoro bloccante).
  2. tokio::select! con tokio::time::interval(30s) emette i frame di keepalive.
  3. Quando spawn_blocking termina, il ciclo di select si chiude.

Swift (runWithKeepalive):

  1. Un Task in background emette frame di avanzamento in un ciclo con Task.sleep(30s).
  2. L’operazione asincrona principale gira in modo concorrente.
  3. Quando l’operazione principale termina, il Task in background viene annullato.

L’approccio di Rust libera esplicitamente i worker di tokio (fondamentale perché il task di scrittura ne ha bisogno). L’approccio di Swift si affida allo scheduling cooperativo della concorrenza strutturata.

Codifica del manifest

Rust: la struct CapManifest viene serializzata in JSON tramite serde_json::to_vec(). Viene passata a CartridgeRuntime::new() o CartridgeRuntime::with_manifest().

Swift: la struct Manifest viene codificata tramite JSONEncoder().encode() in Data. Viene passata a CartridgeRuntime(manifest:).

Il formato JSON è identico sul canale; l’host non vede alcuna differenza tra il manifest di una cartuccia Rust e quello di una cartuccia Swift.

Test

Rust: i test si eseguono tramite cargo test. Il modulo in_process_host.rs fornisce InProcessCartridgeHost e FrameHandler per testare gli handler senza avviare processi. I test di integrazione in integration_tests.rs verificano l’intero protocollo dei frame.

Swift: i test si eseguono tramite i target di test di Xcode. Il package capdag-objc include target di test per i tipi URN, i moduli del planner e la gestione del protocollo.