# Rust SDK 0.5.0

[Read this page in HTML](https://www.scopedb.io/docs/release-notes/2026-11-02-rust-sdk-0-5-0)

Published: 2026-11-02

Rust SDK 0.5.0 follows 0.4.2 with reusable finite writers and structured recovery information for failed writes. It also renames append outcomes and Catalog version fields. Rust 1.91.0 remains supported. For a new application, start with the [Rust SDK guide](https://www.scopedb.io/docs/guides/sdks/rust.md).

## What's new

* Use `Table::append_writer()` to write a finite slice with bounded concurrency. Its report records request input ranges and outcomes, including the unsubmitted suffix after a failure.
* Recover partial progress through `Error`: failed stream admission, finite writes, and stream delivery retain typed reports when propagated with `?`.
* Inspect `AppendOutcome::{Succeeded, Failed, Ambiguous}` and default to retries of validated HTTP `429` admission refusals. Ambiguous writes require reconciliation or explicit acceptance of duplicate-write risk.
* Guard Catalog mutations with `version` and `expected_version`, and inspect `catalog_outcome()` after a failed mutation.
* Resume statement tracking after a lost submission response using the ID retained in `Error::statement_id()`.

## Upgrade from 0.4

Update the dependency and commit `Cargo.toml` and `Cargo.lock`:

```bash
cargo add scopedb-client@0.5.0
```

Applications using 0.3 can upgrade directly to 0.5.0 by applying the [0.3-to-0.4 API migrations](https://www.scopedb.io/docs/release-notes/2026-10-23-rust-sdk-0-4-1.md#upgrade-from-03) followed by the changes below.

### Rename append outcomes and retry options

| In 0.4                                  | In 0.5                                    |
| --------------------------------------- | ----------------------------------------- |
| `AppendState`                           | `AppendOutcome`                           |
| `AppendState::Committed`                | `AppendOutcome::Succeeded`                |
| `AppendState::Rejected`                 | `AppendOutcome::Failed`                   |
| `AppendState::Unknown`                  | `AppendOutcome::Ambiguous`                |
| `AppendDeliveryOutcome::Ok`             | `AppendDeliveryOutcome::Succeeded`        |
| `AppendDeliveryOutcome::Unknown`        | `AppendDeliveryOutcome::Ambiguous`        |
| `AppendStreamBuilder::retry_unknown()`  | `AppendStreamBuilder::retry_ambiguous()`  |
| `AppendDeliveryReport::unknown_rows`    | `AppendDeliveryReport::ambiguous_rows`    |
| `AppendDeliveryReport::unknown_batches` | `AppendDeliveryReport::ambiguous_batches` |
| `AppendStreamStats::unknown_rows`       | `AppendStreamStats::ambiguous_rows`       |

`Succeeded` confirms that all rows in the request committed. `Failed` means no rows took effect or can take effect later. `Ambiguous` means the request may have taken effect or may still take effect later. `AppendOutcome` preserves the serialized values `committed`, `rejected`, and `unknown`; its display text uses `succeeded`, `failed`, and `ambiguous`.

Use `AppendRowsResult::status` and `Error::append_details().status` to read the outcome. HTTP `200` with a valid, matching row count confirms success, including responses without a wire `status` field. Valid modern HTTP `4xx` errors establish definite failure. HTTP `5xx`, interrupted requests, and invalid responses leave the outcome ambiguous.

Only validated HTTP `429` admission refusals qualify for default automatic retries. Older HTTP `503` responses remain ambiguous even when diagnostics say `rejected` or `retryable: true`. `.retry_ambiguous(true)` opts into retrying transient ambiguous failures with possible duplicate rows. Client-side append timeouts are never retried automatically. `Table::append` continues to make one attempt.

Handle a failed request as a whole batch. Structured row diagnostics may be absent; use the typed outcome for recovery and retain the original input for reconciliation. A later definite failure leaves an earlier ambiguous attempt unresolved.

### Rename Catalog version fields

| In 0.4                     | In 0.5                             |
| -------------------------- | ---------------------------------- |
| `CatalogSnapshot::etag`    | `CatalogSnapshot::version`         |
| `CatalogResult::etag`      | `CatalogResult::version`           |
| `CatalogOptions::if_match` | `CatalogOptions::expected_version` |

Pass the opaque token unchanged from a read or dry-run result to `expected_version`. It covers the entire tenant catalog, so changes outside the selected resource can also invalidate it. HTTP `412` with code `precondition_failed` becomes `ErrorKind::Conflict`; read the resource again and revise the change. See [manage table definitions](https://www.scopedb.io/docs/guides/sdks/rust.md#manage-table-definitions) for an example.

Replace checks for the client-generated `catalog_commit_outcome_unknown` code with `Error::catalog_outcome()`. `CatalogMutationOutcome::Rejected` establishes rejection; `Unknown` requires reading the resource before replaying the mutation. Catalog reads, dry runs, and local validation failures have no mutation outcome. `code()` retains the original server code and is absent for client-side failures.

### Update error handling

`ErrorKind` identifies the failure category, while `ErrorOperation` identifies the SDK operation. Update enum matches and retry checks:

| In 0.4                                         | In 0.5                                                    |
| ---------------------------------------------- | --------------------------------------------------------- |
| `ErrorKind::ConfigInvalid`                     | `ErrorKind::InvalidInput`                                 |
| `ErrorKind::StatementCancelled`                | `ErrorKind::Cancelled`                                    |
| `AppendRowsFailed` / `CatalogFailed`           | Failure category plus `operation()` and typed outcome     |
| `ErrorKind::StatementFailed`                   | Failure category plus `statement_details()`               |
| `ErrorKind::Unexpected`                        | Specific category such as `InvalidResponse` or `Internal` |
| `is_temporary()` / `is_persistent()`           | `is_retryable()` plus typed progress and outcomes         |
| `is_permanent()`                               | Failure category and `is_retryable()`                     |
| `ErrorKind::into_static()` / string conversion | `ErrorKind::as_str()`                                     |

`is_retryable()` describes whether repeating the same SDK call as-is is safe and useful. It returns `false` for uncertain writes and aggregate failures carrying partial-progress reports. Inspect `retries()` and `retry_exhausted()` on failed append requests to distinguish a retryable failure from an exhausted retry budget.

Error constructors, context mutation, and retry-state setters are internal. Represent application-specific failures with your own error type, retaining the SDK error as a source. SDK operations return `Result<T, Error>` and preserve their recovery data through `?`.

### Recover partial progress and submitted statements

| Failed operation                       | Recovery data                                            |
| -------------------------------------- | -------------------------------------------------------- |
| `AppendStream::send_all()`             | `append_admission()` records the admitted prefix         |
| `AppendWriter::write()`                | `append_write_report()` and `append_input_index()`       |
| `AppendStream::flush()` / `shutdown()` | `append_delivery_report()` records the delivery interval |
| Statement submission or waiting        | `statement_id()` identifies the existing execution       |

Rows already admitted to a stream remain owned by that stream. Observe delivery through `flush()` or `shutdown()` before replaying them. A failed delivery report includes committed, failed, ambiguous, and dropped row counts since the previous completed barrier; concurrent terminal waiters share the report. Finite-write reports retain input ranges and the unsubmitted suffix. See the [Rust error-handling guide](https://www.scopedb.io/docs/guides/sdks/rust.md#handle-errors).

After a lost statement submission response or a later polling failure, use the retained ID with `Client::statement_handle()` to resume tracking. `with_statement_id()` still accepts an application-owned ID.

### Review panic boundaries

`Error`, `AppendLastFailure`, `AppendStreamStats`, and `AppendBatchFailureEvent` no longer implement `UnwindSafe` or `RefUnwindSafe`. Review `catch_unwind` closures capturing these values. Keep errors outside the closure or capture owned diagnostic text where possible. If a closure must capture the error, review the state observable after a panic before choosing `AssertUnwindSafe`.

## Related contents

* [All release notes](https://www.scopedb.io/docs/release-notes.md)

* [Rust SDK](https://www.scopedb.io/docs/guides/sdks/rust.md)

* [Rust API reference](https://docs.rs/scopedb-client/0.5.0/scopedb_client/)
