Skip to content

Commit

Permalink
<docs> updated /lib/raii.md
Browse files Browse the repository at this point in the history
  • Loading branch information
hiemstar committed Jun 5, 2024
1 parent 12f6128 commit 296ba5c
Showing 1 changed file with 25 additions and 8 deletions.
33 changes: 25 additions & 8 deletions lib/raii.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ A.methods.__dtor(self : &A)
```
These methods support the implementation of smart containers and smart pointers, like *std::string*, *std::vector* and *std::unique_ptr*, *std::shared_ptr*, *boost:offset_ptr* in C++.

The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular C stdlib functions such malloc and free, leaving memory allocation in the hands of the programmer.
The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular *C stdlib* functions such as malloc and free, leaving memory allocation in the hands of the programmer.

If implemented, these methods are inserted judiciously during the type checking phase, implemented in *terralib.lua*. All these metamethods can be implemented as macro's or as terra functions.

## Compiler supported methods for RAII
A managed type is one that implements at least `__dtor` and optionally `__init` and `__copy` or, by induction, has fields or subfields that are of a managed type. In the following I assume `struct A` is a managed type.

To enable RAII, import the library */lib/terralibext.t* using
```
require "terralibext"
```
The compiler only checks for `__init`, `__dtor` and `__copy` in case this libreary is loaded.

### Object initialization
`__init` is used to initialize managed variables:
```
Expand All @@ -32,7 +38,7 @@ A managed type is one that implements at least `__dtor` and optionally `__init`
The compiler checks for an `__init` method in any variable definition statement, without explicit initializer, and emits the call right after the variable definition, e.g.
```
var a : A
a:__init()
a:__init() --generated by compiler
```
### Copy assignment
`__copy` enables specialized copy-assignment and, combined with `__init`, copy construction. `__copy` takes two arguments, which can be different, as long as one of them is a managed type, e.g.
Expand All @@ -51,20 +57,20 @@ or
```
a = b ----> A.methods.__copy(b, a)
```
`__copy` can be an overloaded terra function or a macro.
`__copy` can be a (overloaded) terra function or a macro.

The programmer is responsable for managing any heap resources associated with the arguments of the `__copy` method.

### Copy construction
In object construction, `__copy` is combined with `__init` to perform copy construction. For example,
```
var b = a
var b : B = a
```
is replaced by the following statements
```
var b : B
b:__init() --generated by compiler if an `__init` is implemented
A.methods.__copy(a, b)
b:__init() --generated by compiler if `__init` is implemented
A.methods.__copy(a, b) --generated by compiler
```
If the right `__copy` method is not implemented but a user defined `__cast` metamethod exists that can cast one of the arguments to the correct type, then the cast is performed and then the relevant copy method is applied.

Expand Down Expand Up @@ -105,7 +111,19 @@ is replaced by
## Compositional API's
If a struct has fields or subfields that are managed types, but do not implement `__init`, `__copy` or `__dtor`, then the compiler will generate default methods that inductively call existing `__init`, `__copy` or `__dtor` methods for its fields and subfields. This enables compositional API's like `vector(vector(int))` or `vector(string)`. This is implemented as an extension to *terralib.lua* in *lib/terralibext.t*.

## Examples
The following files have been added to the terra testsuite:
* *raii.t* tests whether `__dtor`, `__init`, and `__copy` are evaluated correctly for simple datatypes.
* *raii-copyctr-cast.t* tests the combination of `metamethods.__cast` and `__copy`.
* *raii-unique_ptr.t* tests some functionality of a unique pointer type.
* *raii-shared_ptr.t* tests some functionality of a shared pointer type.
* *raii-offset_ptr.t* tests some functionality of an offset pointer implementation, found e.g. in *boost.cpp*.
* *raii-compose.t* tests the compositional aspect.

You can have a look there for some common code patterns.

## Current limitations
* The implementation is not aware of when an actual heap allocation is made and therefore assumes that a managed variable always carries a heap resource. It is up to the programmer to properly initialize pointer variables to nil to avoid calling 'free' on uninitialized pointers.
* Tuple (copy) assignment (regular or using `__copy`) are prohibited by the compiler in case of managed variables. This is done to prevent memory leaks or unwanted deletions in assignments such as
```
a, b = b, a
Expand All @@ -126,10 +144,9 @@ terra foo()
end
```
If `bar` would return `b` then its associated heap resources would be released before they can be used in the outer scope.
* The implementation is not aware of when an actual heap allocation is made and therefore assumes that a managed variable always carries a heap resource. It is up to the programmer to properly initialize pointer variables to nil to avoid calling 'free' on uninitialized pointers.

## Roadmap
The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is to:
* support *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value.
* support borrow checking (similar to *rust*) by counting, at compile time, the number of references.
* introduce a `__new` method that does heap allocation for the programmer. This way the compiler is made aware of all heap allocations being made.
* introduce a `__new` method that signals a heap allocation. This way the compiler is made aware of all heap allocations being made.

0 comments on commit 296ba5c

Please sign in to comment.