# Rust API and C# migration guide `MetaCrate` is a native Rust implementation of the API and behavior cataloged from `LibreMetaverse`. The compatibility reference is the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba); it is never loaded, invoked, or shipped. Exact type/member mappings live in [`RUST-TYPES.tsv`](../api/RUST-TYPES.tsv) and [`RUST-MAPPING.tsv`](../api/RUST-MAPPING.tsv). Search those files by C# documentation ID when a familiar member has a non-obvious Rust name. ## Choosing crates and features Most applications start with `libremetaverse` and add narrower crates only when they use those APIs directly. | Crate | Choose it for | Features or system boundary | | --- | --- | --- | | `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7; optional `jpeg2000` and `vorbis` | | `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust | | `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust | | `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` | | `libremetaverse-imaging-skia` | Common raster formats through Skia | Optional `skia`; native Skia build/cache | | `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust | | `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust | | `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust | | `libremetaverse-lsl-tools` | LSL lexing, parsing, diagnostics, and generation | Pure Rust | | `libremetaverse-rlv` | RLV commands, restrictions, locks, camera, and inventory policy | Pure Rust | | `libremetaverse-utilities` | Compatible utility helpers | Pure Rust | | `libremetaverse-voice-vivox` | Vivox XML control protocol | External Vivox service is explicit and never spawned | | `libremetaverse-voice-webrtc` | Native ICE/DTLS/SRTP/SCTP and Opus voice | System libopus; optional `real-audio` uses CPAL | | `libremetaverse-openjpeg` | Audited `OpenJPEG` adapter | System `OpenJPEG` 2.5.4 or newer | | `libremetaverse-opus` | Audited Opus encoder/decoder adapter | System libopus | Features are additive. Keep defaults unless you need a codec, and enable one native adapter at a time while diagnosing installation problems. The [release CI matrix](release-ci-matrix.md) records every validated combination. ## Naming and overload migration C# `PascalCase` types remain recognizable while methods and properties use Rust `snake_case`. A property getter becomes `name()` and its setter becomes `set_name(value)`. Events become `subscribe_*` methods returning an owned subscription. Rust has no overloads, so the simplest form keeps the base name and additional forms receive parameter-derived suffixes. The mapping ledger is authoritative; do not guess a long suffix. ```rust use libremetaverse::InventoryItem; use libremetaverse_types::UUID; let id = UUID::new_with_u_int64(42)?; let mut item = InventoryItem::new_with_uuid(id)?; item.base.set_name("Migrated item".into()); assert_eq!(item.base.uuid(), id); assert_eq!(item.base.name(), "Migrated item"); # Ok::<(), libremetaverse::Error>(()) ``` C# `null` usually maps to `Option`, `ref`/`out` may become a return value or an explicit mutable reference, and interface objects become `dyn Trait` behind `Arc` or `Box` according to the ledger's `ownership` column. ## Ownership and disposal Managers cloned from a `GridClient` share native state. `GridClient` owns its cancellation root and cached services; event guards own registrations; session objects own tasks and sockets. Explicit shutdown is recommended because it can report errors, while `Drop` remains the final idempotent safety net. ```rust use libremetaverse::{ClientLifecycleState, GridClient}; let client = GridClient::new()?; assert_eq!(client.lifecycle_state(), ClientLifecycleState::Active); client.dispose_with_method()?; client.dispose_with_method()?; // idempotent assert_eq!(client.lifecycle_state(), ClientLifecycleState::Disposed); # Ok::<(), libremetaverse::Error>(()) ``` The runnable version is [`offline_client.rs`](../crates/libremetaverse/examples/offline_client.rs). Never hold a manager lock while calling user code or awaiting I/O. Services registered through `GridClient::builder()` must join every owned task before their `shutdown` method returns. ## Async work and cancellation Async APIs borrow no hidden runtime. Call them from the application executor and pass `CancellationToken` explicitly where the C# API accepted one. Cloning a token is cheap and observes the same cancellation source. Cancellation is a typed `Error::Cancelled`, not a successful empty response. ```rust use libremetaverse_types::compat::CancellationTokenSource; # #[tokio::main(flavor = "current_thread")] # async fn main() -> Result<(), libremetaverse::Error> { let source = CancellationTokenSource::new(); let token = source.token(); source.cancel(); token.cancelled().await; assert!(matches!( token.throw_if_cancellation_requested(), Err(libremetaverse::Error::Cancelled) )); # Ok(()) # } ``` The same code is available as [`cancellation.rs`](../crates/libremetaverse/examples/cancellation.rs). Timeouts belong at the caller or documented operation boundary; cancellation must still drain and join the underlying resource owner. ## Errors Fallible compatibility APIs return the shared `libremetaverse::Error`; native composition APIs may expose a narrower error such as `ClientCoreError` or `WebRtcError`. Match variants for control flow and use display text only for operators. Errors and `Debug` output redact credentials, capability URLs, and session secrets. ```rust use libremetaverse::http::DownloadRequest; use libremetaverse_types::compat::Uri; let result = DownloadRequest::new(Uri("file:///not-http".into()), None, None); assert!(matches!(result, Err(libremetaverse::Error::Argument))); ``` Do not translate C# exception swallowing into `unwrap_or_default()`. Preserve the mapped error contract and handle cancellation separately from protocol, I/O, authentication, and validation failures. ## Events and subscriptions An `EventHandler` is an `Arc` callback. Keep the returned `Subscription` for exactly as long as notifications are wanted; dropping it unregisters the callback. Dispatch clones the handler list and releases internal locks before calling user code. Manager event dispatch isolates subscriber panics so one consumer cannot stop later consumers. ```rust use libremetaverse::{GridClient, Inventory, InventoryFolder}; use libremetaverse_types::UUID; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; let client = Arc::new(GridClient::new()?); let inventory = Inventory::new_with_grid_client_uuid(Arc::clone(&client), UUID::zero())?; let calls = Arc::new(AtomicUsize::new(0)); let observed = Arc::clone(&calls); let subscription = inventory.subscribe_inventory_object_added(Arc::new(move |_| { observed.fetch_add(1, Ordering::AcqRel); })); let folder = InventoryFolder::new(UUID::new_with_u_int64(7)?)?; inventory.update_node_for(&folder)?; assert_eq!(calls.load(Ordering::Acquire), 1); drop(subscription); client.dispose_with_method()?; # Ok::<(), libremetaverse::Error>(()) ``` ## Threading and callbacks Public shared managers use `Arc`, atomics, mutexes, channels, and cancellation tokens rather than a CLR synchronization context. A type being cloneable does not make a callback re-entrant: keep handlers short, move expensive work to a bounded queue, and never block an async runtime thread waiting for itself. Download, inventory, client, and voice diagnostics expose owned task/queue counts for shutdown checks. The [concurrency audit](concurrency-hardening.md) defines the exact baseline and soak thresholds. ## Security boundaries The released graph contains no CLR host, .NET assembly loader, subprocess RPC, or foreign `LibreMetaverse` bridge. HTTP uses rustls. Untrusted LLSD, archive, image, packet, event, and signaling inputs have explicit size/depth limits. Secrets belong in operation arguments or environment variables, never in diagnostics, evidence, filenames, command lines, or committed fixtures. Offline/fake modes are real deterministic executions, not skipped live tests. Live login and every mutating smoke action require their own opt-in and literal confirmation. See the [live-grid boundary](live-grid-smoke.md). ## Native prerequisites The default client build needs no image-codec system library. Optional native features require: - `OpenJPEG` 2.5.4 or newer for `jpeg2000`; - the rust-skia prerequisites/cache for `skia`; - system libopus for WebRTC voice and the `libremetaverse-opus` adapter; - ALSA development headers on Linux, `CoreAudio` on macOS, or WASAPI on Windows when `real-audio` enables CPAL; - the Vorbis encoder build prerequisites for `vorbis`. Discovery uses `pkg-config` on Unix/macOS and `vcpkg` on Windows MSVC where applicable. The adapters remain cross-platform; Gitea workflows intentionally run only on `ubuntu-latest`. ## Live `OpenSim` setup Use a dedicated `OpenSim` account and put credentials in the workspace `.env` or the process environment. The login URL is used directly; no Second Life host is substituted. ```text GRID_USER=First Last GRID_PASSWORD=... GRID_LOGIN_URL=https://your-opensim.example/login ``` Start with the credential-safe audit and fake smoke, then opt into live login: ```sh cargo run -p libremetaverse-programs --bin live-grid-smoke -- --audit-only cargo run -p libremetaverse-programs --bin live-grid-smoke -- --fake cargo run -p libremetaverse-programs --bin live-grid-smoke -- \ --allow-live-login --confirm-live-login LOGIN ``` Chat, movement, and reversible inventory each require additional confirmations documented in the [`OpenSim` live-grid guide](live-grid-smoke.md). A test that needs live credentials must fail clearly when they are absent; it must not silently skip. ## Programs and operational tools All pinned upstream programs have native Rust targets and deterministic offline tests. Their arguments, exit statuses, fake/live boundaries, and focused test commands are linked from the [program operations manual](../programs/README.md): - [`osd-inspector`](../programs/README.md#osdinspector) - [`simple-bot`](../programs/README.md#simplebot) - [`packet-dump`](../programs/README.md#packetdump) - [`prim-inspector`](../programs/README.md#priminspector) - [`inventory-explorer`](../programs/README.md#inventoryexplorer) - [`irc-gateway`](../programs/README.md#ircgateway) - [`test-client`](../programs/README.md#testclient) - [`vivox-test`](../programs/README.md#vivoxtest) - [`webrtc-test`](../programs/README.md#webrtctest) For services, run the client under an external supervisor, propagate shutdown cancellation, bound queues/files, collect sanitized evidence, and call logout before disposal. The tool never invents persistence or retry policy on behalf of the application. ## Documentation validation The complete documentation gate is: ```sh cargo run --locked -p metacrate-ci-matrix -- documentation-audit \ --evidence /tmp/metacrate-documentation.json RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked -j 1 cargo test --locked -p libremetaverse --doc -j 1 cargo test --locked -p libremetaverse --examples -j 1 cargo run --locked -p libremetaverse --example offline_client cargo run --locked -p libremetaverse --example cancellation ``` The generated [coverage report](../api/DOCUMENTATION-COVERAGE.md) proves that every mapped member/type retains its exact C# concept ID and mapping context, all public crates have root docs, all local Markdown links resolve, all programs are linked, and every Rust fence in this guide is compiled as a doctest.