Unboxing fields
Opened this issue · 1 comments
We should provide a way to unbox fields.
The problem we should solve is the indirections added as you "wrap" a type in other types, to add functionality.
For example, I have an immutable string type in the standard library:
type Str:
# UTF-8 encoding of the string.
data: Array[U8]
and I want to implement another string type in another library that adds a memoized hash code field to the type:
# A string with memoized hash code.
type HashStr:
str: Str
hash: Option[U64]
This implementation will have a layer of indirection when accessing the string.
Another example would be implementing a stack using a Vec
:
# Same as `Vec`, but doesn't allow random access.
# Not an alias to avoid using it as `Vec`.
type Stack[T]:
vec: Vec[T]
fn Stack.push(self, elem: T) = self.vec.push(elem)
fn Stack.pop(self): Option[T] = self.vec.pop()
Note that in this example fields of the Vec
in array
will change as a result of some of the operations: push
will re-allocate a new array and update the Vec
's array field.
Another, and very common, example is iterator types: array and array iterator, string in character iterators etc. For example:
type StrIter:
str: Str # indirection when accessing the str contents
byteIdx: Usize
type VecIter[T]:
vec: Vec[T] # indirection when accessing the vec contents
idx: Usize
Notes
Unboxing a type in a field is only possible if the unboxed type has no mutable fields, or the type with the unboxed field owns (i.e. initializes with the fields) the value.
As an example to the first case:
type Str:
data: Array[U8]
type StrIter:
str: Str
byteIdx: Usize
fn StrIter.new(str: Str) =
StrIter(str = str, byteIdx = 0)
Since strings are immutable (we don't have a syntax for mutable/immutable fields yet), the data
field will never change once initialized. So StrIter
constructor can take a Str
argument and unbox it in its str
field.
As an example to the second case:
type Vec[T]:
array: Array[T]
len: Usize
type Stack[T]:
vec: Vec[T]
Stack
can't take a Vec
argument in its constructor and unbox it. The argument may be aliased, and other references to it can push elements and cause the len
and array
fields to change.
However it can unbox the Vec
field if it allocates it (so there are no other aliasing at the time of allocating Stack
), and we allow interior pointers so others can alias the Vec
in Stack
(e.g. if we provide a method that returns self.vec
).
A variation of the second idea is we allow unboxing just one field, and generate code so that the values of the unboxing type can work as a value of the unboxed type. In the example above, this means Stack
can be passed to a function that expects Vec
. I.e. non-coercive subtyping, this is basically the approach some OO langs take when extending classes.
A variation of the second idea is we allow unboxing just one field, and generate code so that the values of the unboxing type can work as a value of the unboxed type. In the example above, this means Stack can be passed to a function that expects Vec. I.e. non-coercive subtyping, this is basically the approach some OO langs take when extending classes.
Relevant Rust RFC from 2014: RFC 223.