Files
ada-spark-rfcs/features/array_slice_access.md

151 lines
4.8 KiB
Markdown

- Feature Name: Array Slice Access
- Start Date: 2023-02-13
- Status: Implementation
Summary
=======
Add the ability to create an access to a continuous sub part of an array
(a.k.a. a slice):
```ada
type Arr_Type is array (Natural range <>) of UInt8;
type Arr_Access is access all Arr_Type;
Arr : aliased Arr_Type := (1 .. 10 => 42);
Ptr : Arr_Access;
begin
Ptr := Arr (4 .. 6)'Access; -- New feature
pragma assert (Ptr.all'Length = 3);
```
It is possible to make a sub-slice from a slice:
```ada
Ptr := Ptr(Ptr'First + 1 .. Ptr'Last)'Access;
```
But growing the slice out of its bounds is not allowed as the original bounds
of the underlying array are not know from the slice.
Motivation
==========
As of today, there is no good solution to have an access to a slice of an array
in Ada. There are workarounds, which are covered below in the "Alternative"
section, but none of them provide the safety we can expect from Ada/SPARK. The
ability to work on slices of arrays is very useful in many cases, some of them
presented below. Having this feature built in the language will improve the
quality of software developed in Ada/SPARK.
Zero-Copy parser
----------------
Zero-copy is an optimization technique for parser/deserializers to save time
and memory usage. It can also eliminate the need for dynamic memory which makes
it very interesting for embedded and safety-critical applications. Looking for
example at an IP protocol parser. The parser receives an array that contains a
full IP packet, it will read the meta data and pass the payload to the next
protocol layer (TCP, UDP, etc.). Instead of allocating a new buffer for the
payload and copying the payload from the IP packet to this new buffer, a
zero-copy parser will pass a slice of the original array to the next protocol
layer. Thus saving time and memory. See
[here](https://manishearth.github.io/blog/2022/08/03/zero-copy-1-not-a-yoking-matter/#zero-copy-deserialization-the-basics)
for another example.
Interrupt based data transfer
-----------------------------
In low-level embedded applications and/or operating-systems, incoming data
will sometimes be automatically written in a memory buffer by a peripheral.
This is known as Direct Memory Access (DMA). The use of DMA can greatly improve
performances by avoiding costly interrupts and CPU usage for every single byte
of a data transfer. One sub-system of the software will be in charge of feeding
buffers to the DMA engine. This means having a queue of slices to be
transferred, or the other ways around a queue of slices to fill with incoming
data. One example of such a sub-system is the
[bbqueue-spark](https://github.com/Fabien-Chouteau/bbqueue-spark) library.
Guide-level explanation
=======================
Reference-level explanation
===========================
Rationale and alternatives
==========================
There are two main ways to workaround the lack of slice access in Ada. First,
some variation of:
```ada
type Arr is array (Natural range <>) of UInt8;
type Arr_Access is access all Arr;
type Slice is record
A : Arr_Access;
First : Natural;
Last : Natural;
end record;
```
The problem with this approach is that nothing prevents accessing all of the
original array. It's actually fairly easy for a programmer to forget about the
Slice bounds and in good faith do something like:
```ada
S.A (S.A'First) := 0;
```
Instead of:
```ada
S.A (S.First) := 0;
```
The other option is to use System.Address:
```ada
type Slice is record
Addr : System.Address;
Size : Natural;
end record;
```
This might be ok for memory chunks, but it is also error prone and doesn't
carry the type of the array.
Drawbacks
=========
These new kinds of accesses will require a new fat-pointer implementation to
carry the bounds of the slice.
Prior art
=========
[Rust has built-in slice
types](https://doc.rust-lang.org/book/ch04-03-slices.html) with similar
features as this proposal, with the added lifetime checks of course.
[Go's definition of slices](https://go.dev/blog/slices-intro) looks similar at
first, but have noticeable differences. The underlying array is not visible to
the user and is implicitly created with the slice. Slices have a length and a
capacity, length is the current size of the slice, and capacity the actual size
of the underlying array. So slices can grow and shrink within the capacity of
the array.
Unresolved questions
====================
This features requires a new kind of fat pointers. One open question is whether
or not the slice access feature (and therefore the use of different fat
pointers) will be enabled by default or only if the programmers request it with
a pragma/aspect.
Second open question: do the elements have to be aliased?
```ada
type Arr_Type is array (Natural range <>) of aliased UInt8;
```
Since this is not needed for an access to the full array so we expect to keep
it the same for access to slices.