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):
- La closure gira su
tokio::task::spawn_blocking(pool di thread dedicato al lavoro bloccante). tokio::select!contokio::time::interval(30s)emette i frame di keepalive.- Quando
spawn_blockingtermina, il ciclo di select si chiude.
Swift (runWithKeepalive):
- Un
Taskin background emette frame di avanzamento in un ciclo conTask.sleep(30s). - L’operazione asincrona principale gira in modo concorrente.
- Quando l’operazione principale termina, il
Taskin 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.