core22 min studyLesson 122 in Engineering

Ownership and borrowing: make release responsibility explicit

Use moves, borrows, and explicit copies to design resource lifecycles without confusing a reference with an owner.

On this page

What you’ll understand

  • Distinguish transfer of ownership from temporary access and independent copying.
  • Connect borrow duration to referent validity without treating annotations as runtime extensions.
  • Compare a shared payload with explicit copies using a stated memory boundary.

A useful ownership design answers two questions separately: who is responsible for releasing a resource, and who is allowed to use it right now? A transfer changes responsibility; a borrow grants access under constraints; a copy creates another value whose relationship to underlying resources depends on its type. These distinctions are more reliable than guessing ownership from how many variables mention an address.

Transfer responsibility without duplicating it

Consider a request buffer moving from a receiver to a decoder. If the decoder becomes the owner, the receiver must stop treating its old handle as an independently releasable resource. Otherwise both components might release the same allocation. Rust makes this transfer explicit through moves of non-Copy values; the original binding cannot subsequently be used as if ownership remained there. This is a language-level contract, not a claim that a physical payload is always copied or that its address always stays fixed during a move.

Copying a handle is not necessarily copying its payload. A cloned reference-counted handle can share the original allocation, whereas cloning an owned byte vector creates an independent element buffer. The API determines which operation occurs. Count release responsibilities and distinct allocations, not merely variable names. Also avoid equating ownership with stack placement: an owning value can contain a pointer to heap storage, and a compiler can optimize away physical storage while preserving the required observable semantics.

B⊆V,B=[b0,b1),V=[v0,v1)B\subseteq V,\qquad B=[b_0,b_1),\quad V=[v_0,v_1)
B models the program region needing a borrow and V the region in which the referent remains valid. Endpoints represent ordered program points here, not a runtime lifetime counter. Inclusion is necessary but does not establish exclusive access or every language-specific validity rule.↔ Scroll wide equations horizontally.

Borrowing grants access under a contract

A decoder can inspect a caller-owned buffer without acquiring release responsibility. In safe Rust, a shared reference normally supports shared reading, while an exclusive mutable reference controls access to its referent during the relevant borrow. Borrow checking considers required uses rather than blindly making every borrow last until the closing brace. A lifetime parameter relates valid regions across an interface. It does not allocate storage, insert reference counting, or extend a local temporary so that a returned reference suddenly becomes sound.

Returning an owned result is often the right answer when a function constructs new data. Returning a borrow is appropriate when the result genuinely refers to data whose owner can outlive the caller's use. Choose based on the relationship you can prove, not on a preference for avoiding all copies. If a long-lived cache needs independent data, an explicit copy may be simpler and cheaper than retaining a very large source buffer through a tiny borrowed view. That is a retention tradeoff to measure.

Mshared=S,Mcopies=kSM_{\rm shared}=S,\qquad M_{\rm copies}=kS
S is one equal-sized payload in bytes and k is a positive integer number of independent payload copies. The shared expression counts the original allocation once; both estimates exclude handles, metadata, extra capacity, synchronization, and any independently allocated contents.↔ Scroll wide equations horizontally.

Lifecycle design includes exceptional paths

Describe ownership at each boundary in ordinary words: the callee borrows for the duration of the call, retains an owned handle after return, or takes responsibility for destruction. Include early returns and partially initialized results. Destructors can provide deterministic cleanup along supported control-flow paths, but process termination, intentional leaks, and language-specific abort behavior prevent a universal promise that every destructor always runs. Safe Rust permits memory leaks; memory safety does not require bounded memory consumption or successful application-level cleanup.

In the queue lab, imagine each waiting request holding the sole owner of its input payload. Press Reset, set λ=3 and μ=1, predict a net increase of two retained payloads per second, then Play or Step. For the second experiment, press Reset before setting λ=1 and μ=3; predict that the restored four-job initial backlog drains in two seconds, then Play or Step. Changing ownership syntax cannot repair this sustained overload. The fluid diagram does not implement borrowing, moves, or destruction. Its purpose is to test the separate claim that retaining fewer waiting requests lowers their payload budget. Explain where a borrowed view would keep a larger owner's storage alive even after most contents were no longer needed.

Choose the boundary for independent copies

Four consumers need a 2 MiB payload. Compare one shared immutable allocation with four independent equal-sized copies, ignoring all bookkeeping.

  1. Count distinct payload allocations. Four shared handles still refer to one 2 MiB payload, provided the actual API shares its backing storage.

  2. Four independent copies retain 4 × 2 = 8 MiB. Relative to the shared case, the payload increase is 6 MiB; this does not include a separately retained original.

  3. If the original remains alongside all four copies, peak payload becomes 10 MiB. State whether the consumers require mutation, independent retention, or synchronization before selecting the design.

The result

The memory comparison depends on what the API copies and what remains live. Shared access can reduce payload while introducing retention or coordination costs.

Explore the model

Interactive lab

Interactive lab · queue

Owners waiting for service

Press Reset, set λ=3 and μ=1, predict backlog growth of two jobs per second, then Play or Step. For a separate run, press Reset, set λ=1 and μ=3, predict the restored four-job backlog drains in two seconds, then Play or Step. Associate one owned payload with each waiting job. The model does not enforce ownership or borrowing.

Owners waiting for serviceFluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Current readings: Fluid backlog: 4; Net rate · λ − μ: −1 /s; M/M/1 mean residence: 1 s. All parameters are adjustable with the labeled controls below.02.557.51001.534.56Elapsed time · sFluid backlogλ < μ
Fluid backlogCurrent time
Fluid backlog
4
Net rate · λ − μ
−1 /s
M/M/1 mean residence
1 s
t = 0 s

Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forA valid ownership contract prevents some release mistakes, while queue bounds control how many valid owners remain live.

Check your understanding

What does a Rust lifetime annotation on a returned reference do?

Try it yourself

A 100 MiB source allocation has a 1 MiB region needed by a cache. Compare retaining a shared view after other work finishes with copying just that region and releasing the source.

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Count the backing allocation retained by the view, not only its visible length.

Compare with the solution

The shared view may retain the entire 100 MiB allocation if that is its ownership model. An independent 1 MiB copy allows the source to be released once every other reader finishes, reducing later retained payload by 99 MiB. The copy briefly overlaps the source, producing 101 MiB at that point, before metadata or capacity.

Take it with you

  • A move transfers ownership; a borrow grants constrained access.
  • Lifetime annotations express validity relationships rather than extending storage.
  • Explicit copies can improve retention when sharing keeps a much larger allocation alive.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.