Browse narratives

EN · · 3 min read

Ownership Is Not Only About Memory Management

A C# developer's model of Rust ownership as responsibility for values, resources, mutation, and concurrency.

Technical GuideIntermediateEvolving

Rust introduces ownership through memory, but memory is only the first place the model becomes visible. Ownership is also a vocabulary for responsibility: who may move a value, who may observe it, who may mutate it, and when cleanup becomes inevitable.

For a C# developer, this matters even in a garbage-collected system. The garbage collector manages object memory; it does not decide who owns a transaction, stream, cancellation source, lock, or background task.

The three rules create a lifecycle

The Rust model can be compressed into three rules: each value has an owner, there is one owner at a time, and the value is dropped when its owner leaves scope. Moves transfer responsibility. Borrows grant temporary access without transferring it.

fn normalize(mut input: String) -> String {
    input.make_ascii_lowercase();
    input
}

fn length(input: &str) -> usize {
    input.len()
}

normalize owns its String and returns ownership. length borrows string data. The signatures communicate different relationships before the body is read.

Borrowing constrains aliases and mutation

Many difficult bugs are not about allocation; they are about changing data while another part of the program assumes it is stable. Rust permits many shared references or one mutable reference for the relevant lifetime.

A compiler rejection can therefore be design feedback. If a value must be mutated from many places, the missing idea may be an owner, a message boundary, or a different state representation — not a more elaborate lifetime annotation.

Ownership appears in C# under other names

C# expresses resource responsibility through conventions and APIs:

  • using couples a resource to a lexical lifetime;
  • dependency-injection scopes define who may retain a service;
  • a CancellationTokenSource needs a clear creator and disposer;
  • a queue needs an owner responsible for completion;
  • a background task needs supervision rather than being forgotten.

Rust makes similar questions unavoidable because the type system participates. C# leaves more room to encode them through API shape, analyzers, tests, and review.

Types can make transitions explicit

struct Draft { body: String }
struct Published { body: String }

impl Draft {
    fn publish(self) -> Published {
        Published { body: self.body }
    }
}

Calling publish consumes the draft, so that exact value cannot be published twice afterward. This typestate approach is not always worth the additional types, but it shows that ownership can encode protocol, not merely deallocation.

Shared ownership moves cost

Rc<T> and Arc<T> allow multiple owners through reference counting. Mutex<T> or RwLock<T> can coordinate mutation. These tools move the question into runtime cost, contention, cycles, and shutdown behavior. Wrapping a design in shared synchronization may make it compile without making responsibility clear.

Bring the question back to engineering

Ask who creates the resource, who may retain it, where mutation happens, what ends its lifetime, and how incomplete work is observed during shutdown. Rust answers some questions in the compiler. Other ecosystems answer them differently. Responsibility should still be visible somewhere.

Further reading