# Memory\ usage guidelines This document describes the relationship between `Memory` and its related classes (`MemoryPool`, `IMemoryOwner`, etc.). It also describes best practices when accepting `Memory` instances in public API surface. Following these guidelines will help developers write clear, bug-free code. ## First, a tour of the basic exchange types * `Span` is the basic exchange type that represents contiguous buffers. These buffers may be backed by managed memory (such as `T[]` or `System.String`). They may also be backed by unmanaged memory (such as via `stackalloc` or a raw `void*`). The `Span` type is not heapable, meaning that it cannot appear as a field in classes, and it cannot be used across `yield` or `await` boundaries. * `Memory` is a wrapper around an object that can generate a `Span`. For instance, `Memory` instances can be backed by `T[]`, `System.String` (readonly), and even `SafeHandle` instances. `Memory` cannot be backed by "transient" unmanaged memory; e.g., it is forbidden to back a `Memory` with `stackalloc`. The `Memory` type is heapable, meaning that it can appear as a field in a class, and it can be used across `yield` and `await` boundaries. There are also `ReadOnlySpan` and `ReadOnlyMemory` types that correspond to read-only versions of `Span` and `Memory`, respectively. ## Owners, consumers, and lifetime management Let's stick a pin in `Memory` for now and speak about buffers in more general terms. Since buffers can be passed around between APIs, and since buffers can sometimes be accessed from multiple threads, we need to introduce lifetime semantics. There are two core concepts. The first concept is **ownership**. The _owner_ of a buffer instance is responsible for lifetime management, including _destroying_ the buffer when it is no longer in use. All buffers have a _single owner_. Generally the owner is the component which created the buffer or which received the buffer from a factory. Ownership can also be transferred; Component A can relinquish control of the buffer to Component B, at which point Component A may no longer use the buffer, and Component B becomes responsible for destroying the buffer when it is no longer in use. The second concept is **consumption**. The _consumer_ of a buffer instance is allowed to _use_ the buffer instance, perhaps writing to or reading from it. Buffers have _one consumer at a time_ unless some external synchronization mechanism is provided. Importantly, _the active consumer of a buffer is not necessarily the buffer's owner_. Consider the following pseudocode, where the `Buffer` type is a stand-in for an arbitrary buffer type. ```cs // Writes 'value' as a human-readable string to the output buffer. void WriteInt32ToBuffer(int value, Buffer buffer); // Prints the contents of the buffer to the console. void PrintBufferToConsole(Buffer buffer); // Application code void Main() { var buffer = CreateBuffer(); try { int value = Int32.Parse(Console.ReadLine()); WriteInt32ToBuffer(value, buffer); PrintBufferToConsole(buffer); } finally { buffer.Destroy(); } } ``` In this pseudocode, the `Main` method creates the buffer so becomes its _owner_, and `Main` is thus responsible for destroying the buffer when it's no longer in use. The buffer only ever has one _consumer_ at a time (first `WriteInt32ToBuffer`, then `PrintBufferToConsole`), and neither of the consumers owns the buffer. Note also that "consumer" in this context does not imply a read-only view of the buffer; consumers can modify buffer contents if given a read+write view of the buffer. ## Memory\ and the owner / consumer model At this point, let's reintroduce `Memory` into the picture, along with one more type: `IMemoryOwner`. The type `IMemoryOwner` is, as its name suggests, the **unit of ownership** of the associated `Memory` instance. If a component has an `IMemoryOwner` reference, then that component _owns_ the buffer. `Memory` is itself the **unit of consumption**. If a component has a `Memory` reference, then that component _consumes_ the buffer. To clarify this point, consider once again the earlier pseudocode, but let's now introduce real types into the system. ```cs // Writes 'value' as a human-readable string to the output buffer. void WriteInt32ToBuffer(int value, Memory buffer); // Prints the contents of the buffer to the console. void PrintBufferToConsole(Memory buffer); // Application code void Main() { IMemoryOwner owner = MemoryPool.Shared.Rent(); try { int value = Int32.Parse(Console.ReadLine()); WriteInt32ToBuffer(value, owner.Memory); PrintBufferToConsole(owner.Memory); } finally { owner.Dispose(); } // Alternatively, with 'using' syntax instead of 'try / finally' using (var owner = MemoryPool.Shared.Rent()) { int value = Int32.Parse(Console.ReadLine()); WriteInt32ToBuffer(value, owner.Memory); PrintBufferToConsole(owner.Memory); } } ``` Again, in this code, the `Main` method holds the reference to the `IMemoryOwner` instance, so the `Main` method is the _owner_ of the buffer. The `WriteInt32ToBuffer` and `PrintBufferToConsole` methods accept `Memory` as a public API, therefore they _consume_ the buffer. (And they only consume it one-at-a-time.) (The observant reader may note that `PrintBufferToConsole` should really accept `ReadOnlyMemory` instead of `Memory` as a method argument. More on this later.) ## Usage guidelines Now that we have the basics down, we can go over the rules necessary for successful usage of `Memory` and related types. In the rules below, we'll generally refer just to `Memory` and `Span`. The same guidance also applies to `ReadOnlyMemory` and `ReadOnlySpan` unless explicitly called out otherwise. ### Rule #1: If writing a synchronous API, accept `Span` instead of `Memory` as a parameter if possible. `Span` is more versatile than `Memory` and can represent a wider variety of contigious memory buffers. `Span` also has better performance characteristics than `Memory`. Finally, `Memory` is convertible to `Span`, but there is no `Span`-to-`Memory` conversion possible. So if your callers happen to have `Memory` instance, they'll be able to call your `Span`-accepting method anyway. Accepting `Span` instead of `Memory` also helps you write a correct consuming method implementation, as you'll automatically get compile-time checks to ensure that you're not attempting to access the buffer beyond your method's lease (more on this later). Sometimes circumstances will necessitate you taking a `Memory` parameter instead of a `Span` parameter, even if you're fully synchronous. Perhaps an API that you depend on has only `Memory`-based overloads, and you need to flow your input parameter down to that method. This is fine, but be aware of the tradeoffs mentioned in the first paragraph in this rule. ### Rule #2: Use `ReadOnlySpan` or `ReadOnlyMemory` if the buffer is intended to be immutable. Consider the `PrintBufferToConsole` method from the earlier sample code. ```cs void PrintBufferToConsole(Memory buffer); ``` This method only reads from the buffer; it does not modify the contents of the buffer. The method signature should be changed to the following. ```cs void PrintBufferToConsole(ReadOnlyMemory buffer); ``` _In fact_, combining this rule and Rule #1 above, we can do even better and rewrite it as follows. ```cs void PrintBufferToConsole(ReadOnlySpan buffer); ``` The `PrintBufferToConsole` method now works with pretty much every buffer type imagineable: `T[]`, `stackalloc`, and so on. You can even pass a `System.String` directly into it! ### Rule #3: If your method accepts `Memory` and returns `void`, you must not use the `Memory` instance after your method returns. TODO: Fill me in.