# Modernizing Critical Microservices with Rust: Strangler Fig and the Financial Return (ROI) of Memory Safety

The payment service pages someone at 02:17. Average response time still looks fine, p99 has climbed, and adding instances has not stopped the pattern from returning. Someone suggests Rust. The useful question is which part of the delay it would remove—and which system would own charging money throughout the migration.

Modernizing critical microservices with Rust means identifying a boundary from CPU/allocation profiles, replacing it incrementally with Strangler Fig, preserving business behavior, and turning measured gains into an actual bill reduction. We use payments as the scenario; narrow hot paths such as parsing, pricing, fraud evaluation, and media processing can use the same decision process.

Two boundaries matter immediately: Rust does not eliminate leaks or every crash, and ordinary safe Go/Java code does not share C++’s memory-corruption risk model. Neither “zero TCO” nor “no GC means flat latency” is an investment thesis. We will distinguish safety guarantees, performance measurements, and financial assumptions throughout.

## 1. The financial meaning of memory safety: which failure class shrinks?

Use-after-free, double-free, and invalid pointer access in a C/C++ service can mean crash analysis, emergency patches, security response, and interrupted on-call shifts. Given sound dependencies and correct unsafe boundaries, Rust’s ownership/borrowing model provides a language-level contract for excluding these classes from safe code. The [Rust Reference](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) defines the relevant undefined behavior. This is stronger than adding isolated null checks.

Ownership is not an income statement. The [Rust book](https://doc.rust-lang.org/book/ch15-06-reference-cycles.html) explicitly documents leaks from reference cycles. Unbounded caches, resource retention, OOM, panic, deadlock, incorrect pricing, and network failures remain possible. [Unsafe/FFI boundaries](https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html) carry additional obligations; Rust cannot verify a C library’s pointer-lifetime behavior on its behalf.

For Java/Go migrations, the hypothesis often concerns allocation pressure, heap scanning, representation overhead, and hot-path CPU. Modern collectors are not merely long stop-the-world pauses. The [Go GC guide](https://go.dev/doc/gc-guide) explains CPU/memory trade-offs and GOMEMLIMIT’s soft-limit behavior; the [G1 tuning guide](https://docs.oracle.com/en/java/javase/21/gctuning/garbage-first-garbage-collector-tuning.html) covers throughput/latency choices. A “Java is slow” claim without collector/runtime versions is not evidence.

Safety and cost boundaries

| Problem | Rust’s effect | Engineering still required |
| --- | --- | --- |
| Use-after-free / double-free | Language-level protection in safe Rust with sound abstractions | Review unsafe, FFI, and dependencies |
| Data race | Safe Rust excludes this UB class | Workflow races and distributed consistency |
| GC work | Removes the need for a tracing GC | Measure allocator, Drop, contention, and scheduling |
| Leaks / OOM | Not eliminated | Bound caches/admission; test memory headroom and soak behavior |
| Incorrect / duplicate payment | Not prevented | Domain validation, idempotency, transactions, reconciliation |

The [Rustonomicon distinction](https://doc.rust-lang.org/nomicon/races.html) matters: data races and general race conditions are different. The borrow checker cannot stop two independent services from processing one payment intent twice. Estimate reliability savings by classifying historical incidents; the entire incident budget cannot be booked as a Rust benefit.

## 2. Profile first: which endpoint is a Rust candidate?

Suppose a 240 ms payment request spends 190 ms at the provider, 35 ms in the database, and 15 ms locally. Making local work three times faster reduces the total to about 230 ms: 240/(225+15/3) ≈ 1.043×. A 67% local reduction becomes roughly 4.2% end-to-end. This illustrative Amdahl check is inexpensive compared with a rewrite.

Use flamegraphs, allocation profiles, and traces to separate actual CPU work from waiting. Record request rate, CPU-seconds/request, allocated bytes/request, RSS, and p50/p95/p99 for the same traffic slice. Label database-pool waiting, DNS/TLS, locks, and cgroup throttling separately. A GC event near a spike is correlation; causal attribution requires runtime logs and controlled comparison.

Selecting the first boundary

| Symptom | First intervention | Rust decision |
| --- | --- | --- |
| CPU-bound parser/codec or pricing engine | Profile representation, copies, and algorithms | A narrow pure-function boundary is a strong candidate |
| Database/network waiting dominates | Queries, indexes, cache, timeout budgets | A language change does not remove waiting |
| Large heap / allocation pressure | Reduce allocation; test collector/heap settings | Try Rust if a material gap remains |
| C/C++ memory-corruption history | Narrow risky components and FFI surface | Memory safety can justify a separate investment |
| Low volume, capable existing team | Retain and simplify the current runtime | Operating a second language may cost more |

Discord’s [2020 Read States migration](https://discord.com/blog/why-discord-is-switching-from-go-to-rust) is useful context for a large live set and GC scanning. Its team also changed data structures and copies. It demonstrates profiling and canary deployment on a focused service, rather than a benchmark of every 2026 Go release or a savings estimate for your payment system.

## 3. Strangler Fig: preserve the contract, replace implementation incrementally

[Strangler Fig](https://martinfowler.com/bliki/StranglerFigApplication.html) introduces a transition boundary and replaces capabilities incrementally. Temporary routing, comparison pipelines, and dual-schema support cost money and need retirement conditions. The ownership and recovery principles from our [legacy modernization guide](/en/blog/legacy-software-modernization-data-migration-plan) still apply.

The first boundary need not charge a card. Extract a pure risk-scoring or fee-calculation stage within /api/v1/process-payment first. Rust accepts versioned inputs and returns a side-effect-free decision while the existing service retains charging ownership. That seam allows comparison without introducing two writers against real money.

```mermaid
flowchart LR
    C["Client: stable payment intent"] --> G["Authenticated API gateway"]
    G --> R["Pinned owner registry / deadline / admission"]
    R --> L["Legacy payment owner"]
    R --> N["Rust payment owner"]
    L --> D["Shared acceptance / idempotency ledger"]
    N --> D
    D --> O["Transactional outbox"]
    O --> P["Payment provider: native idempotency"]
    D --> Q["Status / reconciliation"]
    L -. "Redacted immutable evaluation input" .-> S["Rust shadow evaluator: NO writes"]
    S --> X["Decision diff / business invariants"]
```

Moving the full endpoint requires a shared contract for tenant, payment_intent_id, currency, amount_minor, quote/policy versions, idempotency key, deadline, and status semantics. Avoid floating-point money; define currency exponents, rounding, and overflow. Matching field names do not establish semantic equivalence: accepted and charged must remain distinct states.

```json
{
  "contract_version": 3,
  "payment_intent_id": "00000000-0000-4000-8000-000000000017",
  "amount_minor": 125050,
  "currency": "TRY",
  "quote_version": 17,
  "policy_version": "risk-2026-11",
  "deadline_at": "2026-11-05T09:00:02Z"
}
```

Derive tenant identity from verified access context. Hash canonical domain data and preserve business-intent uniqueness even if a caller changes the idempotency key. An uncertain previous response is not grounds for a new payment intent. Our [idempotent enterprise API blueprint](/en/blog/secure-ai-agent-erp-crm-integration-idempotent-api-outbox-blueprint) explains the transaction/outbox side.

Wire representation is part of the contract. The sample JSON amount fits JavaScript’s safe-integer range, but the full i64 range does not. Enforce a narrower API amount limit or use canonical decimal strings with checked integer parsing. The Rust function’s i64::MAX test below does not establish lossless transport through a JavaScript JSON number.

## 4. Canary routing: retries must not drift between writers

Randomly sending 1% of requests to Rust can fit read-only evaluation, but is insufficient for charging. Retries, rollout changes, or gateway restarts can move one business intent between implementations. Select a deterministic cohort for new intents and pin ownership in the acceptance record. Existing intents retain their owner and result. An externally supplied X-Route: rust header is not authorization.

```sql
-- Illustrative routing registry; NOT the full payment/idempotency transaction.
CREATE TABLE payment_route (
    tenant_id uuid NOT NULL,
    payment_intent_id uuid NOT NULL,
    owner text NOT NULL CHECK (owner IN ('legacy-v3', 'rust-v3')),
    contract_version integer NOT NULL,
    PRIMARY KEY (tenant_id, payment_intent_id)
);

BEGIN ISOLATION LEVEL READ COMMITTED;
INSERT INTO payment_route (tenant_id, payment_intent_id, owner, contract_version)
VALUES ($1, $2, $3, 3)  -- $3 chosen by trusted rollout policy for NEW intent
ON CONFLICT (tenant_id, payment_intent_id) DO NOTHING;

SELECT owner, contract_version FROM payment_route
WHERE tenant_id = $1 AND payment_intent_id = $2;
COMMIT;
-- Gateway forwards to the persisted owner, never to the proposed $3 blindly.
-- Acceptance still validates canonical payload, identity and business uniqueness.
```

A registry alone does not provide exactly-once. Both implementations must honor shared domain acceptance/results, transactions, outbox, and provider idempotency. Specify crash recovery, leases, and fencing where applicable. Caching route resolution may reduce database overhead, but misses, stale ownership, and invalidation must preserve the contract.

```nginx
# Architecture example only; these are dedicated internal application ports.
# payment-router authenticates, enforces limits, resolves pinned ownership,
# and dispatches to legacy or Rust. It is NOT a second payment writer.
upstream payment_router {
    server 127.0.0.1:18081;
    keepalive 16;
}

location = /api/v1/process-payment {
    client_max_body_size 16k;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Idempotency-Key $http_idempotency_key;
    proxy_connect_timeout 200ms;
    proxy_read_timeout 2s;
    proxy_next_upstream off;
    proxy_pass http://payment_router;
}
# All other routes retain their existing upstream.
# Forward only verified identity; strip/overwrite client-supplied internal headers.
```

In the [NGINX proxy contract](https://nginx.org/en/docs/http/ngx_http_proxy_module.html), proxy_read_timeout measures time between reads, not a total request deadline. Propagate the total deadline at application level and budget downstream calls from remaining time. The example disables blind upstream retries. A timeout after possible acceptance calls for intent-status lookup or reconciliation, rather than dispatching a new charge to another writer.

## 5. Shadowing, contract tests, and the real rollback boundary

Shadow traffic must not issue a second payment-provider call. Feed redacted, versioned inputs into a side-effect-free Rust evaluator and compare reason codes, amounts, rounding, and policy outcomes. Freeze clocks, exchange rates, caches, and randomness where required. Do not copy payment tokens, personal data, or production secrets into a second debug store.

Illustrative acceptance gates for incremental migration

| Stage | Evidence | Recovery boundary |
| --- | --- | --- |
| Offline replay | Versioned corpus: overflow, rounding, boundary values | No production effects |
| Shadow evaluator | Investigate semantic differences; no external writes | Disable shadow consumer |
| 1% canary for new intents | p99, errors and business invariants at comparable load; target zero duplicates | Stop new assignment; preserve existing intent owners |
| 5% → 25% → 100% | Representative peaks, soak, incident/recovery drills | Revert new assignment; reconcile accepted work |
| Retire legacy | Retention/replay window and rollback/support decision | Reintroduce a writer only through an explicit migration procedure |

These percentages and the zero-duplicate target describe a rollout policy, not a guarantee or measured success rate. Low-volume 1% cohorts may not support statistical comparison. Choose observation windows and sample sizes against the service SLO. A deployment rollback does not undo a charge; refund/void is a separate auditable operation. Use expand-contract schema changes and retain old-reader compatibility through the transition.

## 6. Rust services still need queue, memory, and CPU bounds

Putting a CPU loop in an async fn does not make it nonblocking. Long computation can occupy Tokio’s executor. The [spawn_blocking contract](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html) calls for limiting CPU-bound parallelism and notes that started blocking tasks cannot be aborted. Use bounded workers/semaphores, admission control, and cooperative cancellation; dropping a waiting future on timeout may leave computation running.

```text
Request budget example — design assumptions, not measured SLOs
  total deadline:          2,000 ms
  authentication/routing:    50 ms budget
  local evaluation:         100 ms budget, bounded queue
  acceptance transaction:   150 ms budget
  provider interaction:     remaining budget, or async status contract

Bound every resource, not just request count:
  payload bytes, decompressed bytes, in-flight requests,
  queue length + wait age, cache entries/bytes, DB connections,
  CPU tasks, downstream concurrency and response bytes.

Never free an admission permit before the actual CPU job stops.
An HTTP disconnect does not cancel an accepted financial operation.
```

Deterministic destruction can itself cause latency when a large tree or cache segment is dropped at once. Allocator locks, page faults, disk, scheduling, and CPU throttling do not need a GC to exist. Compiler confidence helps refactoring; it does not prove correct prices, authorization, or availability. Production acceptance still needs tests, observation, and recovery.

Separate route owner, contract/policy version, and rollout cohort in the canary dashboard. Keep intent/customer IDs out of metric labels, using controlled logs for trace linkage. Observe queue age, rejected admission, CPU throttling, RSS versus allocator data, panic, and reconciliation backlog alongside p99. One error-rate counter cannot distinguish an HTTP 500 from an uncertain payment-provider outcome.

A second language adds maintenance obligations: compiler/MSRV and lockfile policy, dependency updates, an SBOM, unsafe/FFI inventory, and native-library ownership. A smaller binary is not an operating advantage without reviewers who can maintain it. Define panic strategy, graceful shutdown, and post-restart recovery of accepted work in the deployment contract.

## 7. Rust vs. Go vs. Java: TankDev’s controlled HTTP experiment

We did not copy numbers from a language-ranking chart. We ran three small reference implementations against the same deterministic workload on our VPS to examine idle RSS, resource consumption, latency distributions, and build scope together. This is neither an actual payment service nor a framework benchmark; measurement boundaries matter as much as the result.

Same synthetic HTTP job: 800 requests/s, three trials

| Implementation | Median idle RSS (MiB) | Highest sampled load RSS (MiB) | Pooled p50 / p95 / p99 (ms) | Errors / offered requests | Median build (s) |
| --- | --- | --- | --- | --- | --- |
| Rust | 2.082 | 2.094 | 1.214 / 1.802 / 2.047 | 0 / 36,000 | 0.496 |
| Go | 3.957 | 8.629 | 1.219 / 1.814 / 2.101 | 0 / 36,000 | 0.188 |
| Java | 40.629 | 58.996 | 1.198 / 1.783 / 2.034 | 0 / 36,000 | 0.573 |

Each implementation contributes 36,000 requests from three 800-request/s trials. Percentiles use nearest-rank over pooled raw durations rather than averaging three p95 values. Idle RSS comes from /proc/PID/status about two seconds after process readiness, before warm-up. Load RSS is sampled every 100 ms and may miss brief peaks. A Java heap limit is not an RSS limit.

Reproducible test contract

| Field | Condition |
| --- | --- |
| Date / host | 5 November 2026 Türkiye time; same two-logical-CPU VPS, shared with neighboring services |
| Toolchains | rustc 1.75.0 (82e1608df 2023-12-21) (built from a source tarball); go version go1.22.2 linux/amd64; openjdk version "21.0.12.1" 2026-08-18 |
| Job | 4096-byte payload; 64 rounds of wrapping 64-bit FNV-1a; not cryptography or payment logic |
| HTTP path | Loopback HTTP/1.1, fresh TCP connection per request, one serial request handler, no TLS |
| Limits | Each server in its own cgroup: 0.75 CPU-core quota; MemoryMax=512 MiB |
| Runtime settings | Go: GOMAXPROCS=1, GOGC=100, GOMEMLIMIT=128MiB; Java: G1, Xms16m, Xmx128m, ActiveProcessorCount=1 |
| Traffic | 15 s warm-up at 100 requests/s; 15 s each at 100/400/800 requests/s; three trials with rotated implementation order |
| Load generator | Same-host Python asyncio outside server cgroup; planned open-loop arrivals; maximum 128 active clients |
| Latency boundary | Planned arrival through validated response and connection close; includes dispatch/client queue delays |

These are distribution-provided toolchains, not a claim to test the latest 2026 Rust/Go releases. Computation is the same, but TCP/buffering/parsing, allocators, and runtimes differ. The servers use Rust stdlib TCP, Go stdlib TCP, and Java ServerSocket, without Axum, net/http, Spring, or production observability middleware. This is therefore not a language-isolated experiment.

For builds, we first prime caches, then remove output before each of three source compilations. Rust uses rustc -C opt-level=3; Go uses go build; Java uses javac. Java produces bytecode, with JIT work paid at runtime. Dependency downloads, Cargo clean builds, frameworks, and CI queueing are excluded. A one-file result does not predict a large Rust workspace’s build latency.

The important result is that lower idle RSS does not automatically mean lower HTTP p99. Latencies are close in this short allocation-light workload, and client dispatch delay is material. We do not infer GC-caused Go/Java spikes or flat Rust latency. Neither saturation capacity nor hours-long heap behavior was established. GC analysis requires allocation/live-set profiles, longer soak tests, runtime traces, and an external load generator.

A minimal Rust server starting in a few MiB does not mean your actual microservice will fit in 10–15 MB. Add TLS, pools, caches, telemetry, live data, and framework overhead. Raw data also includes 100/400-request/s runs, CPU-seconds, and throttling counters. We did not derive a fleet-reduction percentage from this experiment.

## 8. A small Rust core: test business correctness separately from memory safety

A pure fee function makes a useful first shadow boundary. This example accepts nonnegative minor-unit amounts and 0–10,000 basis points, using half-up rounding. It is an explicit sample contract, not a universal financial standard or pricing policy for every currency. Taxes, exchange rates, refunds, and provider rules require additional tests and versioning.

```rust
pub fn fee_minor(amount_minor: i64, bps: u32) -> Result<i64, &'static str> {
    if amount_minor < 0 || bps > 10_000 { return Err("invalid input"); }
    let product = i128::from(amount_minor) * i128::from(bps);
    let rounded = (product + 5_000) / 10_000; // Nonnegative half-up only.
    i64::try_from(rounded).map_err(|_| "overflow")
}

#[cfg(test)]
mod tests {
    use super::fee_minor;
    #[test] fn rounds_half_up() { assert_eq!(fee_minor(50, 100), Ok(1)); }
    #[test] fn normal_fee() { assert_eq!(fee_minor(125_050, 175), Ok(2_188)); }
    #[test] fn rejects_negative() { assert!(fee_minor(-1, 100).is_err()); }
    #[test] fn rejects_rate() { assert!(fee_minor(100, 10_001).is_err()); }
    #[test] fn preserves_upper_bound() {
        assert_eq!(fee_minor(i64::MAX, 10_000), Ok(i64::MAX));
    }
}
```

We ran the tests for this core; the checksum HTTP benchmark is a separate synthetic fixture. They are not presented as one benchmark. Domain tests, legacy-corpus replay, and property-based invariants complement memory safety because safe Rust can still compute an incorrect price. For this policy, fee ≥ 0 and fee ≤ amount are useful invariants over valid inputs.

## 9. Learning, build latency, and delivery capacity

There is no universal three-month productivity penalty. In Google’s [2023 report of its 2022 experience](https://opensource.googleblog.com/2023/06/rust-fact-vs-fiction-5-insights-from-googles-rust-journey-2022.html), about one-third reported comparable productivity within two months and more than half within four. Slow builds were the leading complaint. These are professional developers’ self-assessments at Google, not a schedule guarantee or objective sprint-velocity measurement for your team.

Plan separately for ownership/borrowing, error design, async, and review skills. An experienced reviewer and a narrow domain make the first service a measurable investment. A training calendar is still an estimate; track issue lead time, review cycles, escaped defects, and incident load against a baseline to assess it.

Wrapping everything in Arc<Mutex<_>> or adding unnecessary clones can appease the compiler while making the design expensive. Decide ownership, task lifetimes, and sharing boundaries early. Ask which failure classes were excluded before execution, rather than treating compilation as proof of correctness. The compiler does not know your business rules.

Measure cargo check, incremental tests, clean release builds, and CI cache misses separately. Evaluate profiles, crate boundaries, proc macros, linking, and caching. PGO/LTO may improve runtime behavior while increasing build work; validate their actual workload effect. A micro-build measurement does not represent a team’s daily waiting time.

## 10. ROI: when does lower RAM become actual savings?

The owner-reported bill for this VPS is USD 100/month. Lower RSS does not reduce that bill while the same host remains provisioned. It may create headroom or defer a scale-up, but unrealized capacity is not cash saved. Lower Kubernetes requests need the same distinction unless billed resources or provisioned nodes actually decrease.

```text
One-time migration investment M:
  implementation + training + parallel operation + validation + tooling

Monthly net benefit B:
  realized infrastructure savings
  + evidence-backed expected incident/risk reduction
  - added operating/maintenance cost

Simple payback (months) = M / B, only when B > 0
12-month simple ROI = (12 * B - M) / M

Do not count the same engineer-hours in both incident savings and staffing savings.
These are undiscounted planning formulas, not a prediction or financial valuation.
```

Illustrative investment: 240 engineering hours × USD 60/hour plus USD 1,600 of transition/tooling equals USD 16,000. A hypothetical 40-host fleet at USD 100/month costs USD 4,000/month. If representative load/SLA validation allows an actual 30% reduction, that saves USD 1,200/month; USD 200/month of added operations leaves USD 1,000 net. Simple payback is 16 months and twelve-month simple ROI is −25%. This fleet is not TankDev’s current infrastructure, and our test does not establish a 30% capacity reduction.

Sensitivity for the same USD 16,000 investment — assumptions only

| Scenario | Realized gross monthly benefit | Added monthly operations | Net monthly benefit | Simple payback |
| --- | --- | --- | --- | --- |
| Low benefit | USD 400 | USD 200 | USD 200 | 80 months |
| Middle assumption | USD 1,200 | USD 200 | USD 1,000 | 16 months |
| Higher benefit | USD 1,800 | USD 250 | USD 1,550 | 10.32 months |
| Bill unchanged | USD 0 | USD 200 | −USD 200 | None under these assumptions |

Reliability value can use historical incident frequency × expected reduction × loss per incident, with ranges and confidence when the reduction is unmeasured. C/C++ memory-safety risk and Java/Go GC optimization are different benefit categories. Keep reduced security exposure separate from current cash expenditure. A useful decision shows which changed assumption would reverse it, rather than maximizing a headline ROI.

## 11. Decision record and downloadable evidence

```yaml
decision: extract one CPU-heavy evaluation boundary into Rust
status: proposed-until-production-gates-pass
alternatives:
  - tune existing runtime, queries, allocation and data structures
  - keep Go/Java and scale within current SLO
  - Rust sidecar/service for pure evaluation
  - full endpoint migration after contract and ownership validation
acceptance:
  - semantic parity on versioned replay corpus
  - bounded resources and representative peak/soak validation
  - measured p99 and error budget at comparable offered load
  - no duplicate financial side effects; reconciliation drill completed
  - realized savings exceed additional operations cost
rollback:
  - stop assigning NEW intents to Rust
  - preserve ownership and recovery of already accepted intents
retirement:
  - remove transitional routing only after retention/replay/support gates
```

[Raw benchmark JSON](/uploads/blog/rust-modernization-benchmark-20261105.json), [per-request timings](/uploads/blog/rust-modernization-request-samples-20261105.jsonl.gz), and the [source/test bundle](/uploads/blog/rust-modernization-reference-20261105.zip) support inspection. The [Markdown article](/uploads/blog/rust-modernization-guide.en.md) preserves tables, while the [Mermaid source](/uploads/blog/rust-modernization-architecture.mmd) makes the architecture reusable. The reference HTTP parsers are controlled loopback fixtures, not internet-facing production servers.

At TankDev, [operational modernization](/en/services/operational-modernization) includes profiling, data ownership, transition boundaries, contract tests, canaries, and cost acceptance. Our [PoC-to-production architecture guide](/en/blog/from-poc-to-production-scalable-architecture-reliability-cost-engineering) covers capacity and SLO context. If your system has a measured hot path, [we can examine it together](/en/contact). The outcome may be a focused Rust migration or a better-tuned existing Go/Java service.

## Frequently Asked Questions

### Does Rust eliminate memory leaks?
No. Reference cycles, deliberate leaks, unbounded caches, and retained resources remain possible. Safe Rust strongly protects against memory-corruption classes; memory/admission limits and soak tests are still necessary.

### Does having no garbage collector guarantee p99 latency?
No. Allocators, destructors, locks, page faults, scheduling, database/network waits, and CPU throttling can affect tails. Attribute GC effects using runtime evidence.

### Is moving from Go or Java to Rust always a safety improvement?
Their ordinary safe code already provides memory-safe models. Rust may address allocation, CPU, representation costs, or specific native boundaries, without fixing authorization, business rules, or distributed consistency.

### Does a Rust microservice really use 10–15 MB RAM?
There is no universal bound. The small stdlib fixture here differs from a production service with TLS, caches, pools, and telemetry. RSS, heap, and cgroup memory are also distinct metrics.

### What should move first under Strangler Fig?
Choose a profiled, narrow domain boundary with a usable correctness corpus. In payments, pure risk/fee evaluation can be a more controlled first step than introducing two charging writers.

### Can a canary cause duplicate payment?
Blind traffic splitting and retries create that risk. Use stable intent, pinned ownership, shared acceptance/idempotency, domain uniqueness, and provider deduplication. A route registry alone does not establish exactly-once.

### Does rolling back Rust undo a charge?
No. Rollback can change new assignments, while accepted work and external effects remain. Use status checks, reconciliation, and a separate refund/void workflow where needed.

### Does learning Rust necessarily take two or three months?
No. Experience, domain, mentoring, async, and FFI requirements change ramp-up. The Google survey cited here is self-reported and does not guarantee a delivery schedule for every team.

### Does compilation imply correctness?
No. The compiler excludes certain memory/type and data-race errors, while incorrect pricing, missing authorization, distributed races, panic, and deadlock remain possible. Domain and system tests are required.

### Does this benchmark prove Rust is faster under every load?
No. It measures small allocation-light HTTP fixtures on one host with selected toolchains/settings. Parser/runtime differences and client delay remain; production capacity and GC-caused tails were not established.

### How is Rust modernization ROI calculated?
Combine one-time investment, realized monthly savings, and added operations. When net monthly benefit is positive, simple payback is investment divided by benefit. Distinguish headroom and reduced risk from realized cash savings.

### Can TankDev modernize a service without rewriting everything?
Within supported access and system contracts, we can design and develop profiling, boundary extraction, Strangler Fig, canary, and recovery workflows. Measured bottlenecks, team capability, and operating cost determine the language decision.