MemoryViews.jl

This package provide the MemoryView type, which is a lightweight and simple view into Memory. The MemoryView type is a useful low-level building block for code that operates on chunks of memory.

Features:

  • Simple and easy to reason about
  • Low-overhead, efficient methods
  • A safer alternative to pointers

The MemoryView type has the following layout:

struct MemoryView{T, M} <: DenseVector{T}
    ref::MemoryRef{T},
    len::Int
end

The M parameter is either Mutable or Immutable, which are unexported but public types defined in this package. MemoryViews also provide the following aliases for convenience:

const MutableMemoryView{T} = MemoryView{T, Mutable}
const ImmutableMemoryView{T} = MemoryView{T, Immutable}

Immutable memory views are immutable, in that they do not support setindex! or other mutating methods. The existence of an ImmutableMemoryView does not protect its underlying data from being mutated through another variable.

Usage

Constructing memory views

Construct memory views from x with MemoryView(x). MemoryViews should be constructable from any type that is stored as an array densely in memory. It can also be constructed from other non-array types that are represented by a chunk of memory (like a String). By default, constructors exists for the memory-backed types in Base:

# Vectors
@assert MemoryView(["a", "b", "c"]) isa MemoryView

# Strings
@assert MemoryView("abc") isa MemoryView

# Even complex nested memory-backed types
@assert MemoryView(view(codeunits(view("abcd", Base.OneTo(2))), :)) isa MemoryView

For values x that are mutable such as Memorys and Arrays (and SubArrays of those), MemoryView(x) return MutableMemoryView:

@assert MemoryView(Int32[1,2,3]) isa MutableMemoryView{Int32}
@assert MemoryView(Memory{String}(undef, 3)) isa MutableMemoryView{String}

For values that are immutable, MemoryView return ImmutableMemoryViews:

@assert MemoryView("abc") isa ImmutableMemoryView{UInt8}

The constructor ImmutableMemoryView(x) will construct an immutable view no matter if the type returned by MemoryView(x) is mutable or not. This is because it's always possible to convert a mutable memory view to an immutable one:

@assert MemoryView(UInt[]) isa MutableMemoryView{UInt}
@assert ImmutableMemoryView(UInt[]) isa ImmutableMemoryView{UInt}

Hence, when adding new constructors for new types, you should only add methods to MemoryView. This should return a mutable memview where possible.

Indexing

MemoryView{T} is a subtype of AbstractVector{T}, and mostly behave like you would expect an abstract vector to behave w.r.t. indexing:

mem = MemoryView([1,2,3,4,5,6,7,8])

println(mem[2])
println(mem[2:4])
println(mem[end])
println(mem[:])

# output
2
[2, 3, 4]
8
[1, 2, 3, 4, 5, 6, 7, 8]

One exception is slicing, which does not copy the underlying data, but simply returns a new view of the same data. To copy explicitly, use copy, which will create a new MemoryView that looks into a copy of the underlying data:

mem1 = MemoryView([1,2,3])
mem2 = mem1[1:3]
mem3 = copy(mem1)
mem1[1] = 3
println(mem2[1])
println(mem3[1])

# output
3
1