#include <ownership.h>
#include <stdio.h>
int main()
{
FILE *owner f = fopen("file.txt", "r");
if (f)
fclose(f);
}
But comparisons are inevitable, and I also think there are lessons learned
in Rust. int * owner p = malloc(sizeof(int));
if (condition)
free(p);
free(p); // error p may be initialized/moved.
to fix int * owner p = malloc(sizeof(int));
if (condition)
{
free(p);
p = 0;
}
free(p); #include <stdlib.h>
#include <string.h>
static inline void* allocate_and_copy(void* s, size_t n) {
void* p = malloc(n);
if (p) {
memcpy(p, s, n);
}
return p;
}
#define NEW(...) (typeof(__VA_ARGS__)*) allocate_and_copy(&(__VA_ARGS__), sizeof(__VA_ARGS__))
#pragma expand NEW
struct X {
const int i;
};
int main() {
auto p = NEW((struct X) {});
}
The generated code is #include <stdlib.h>
#include <string.h>
static inline void* allocate_and_copy(void* s, size_t n) {
void* p = malloc(n);
if (p) {
memcpy(p, s, n);
}
return p;
}
#define NEW(...) (typeof(__VA_ARGS__)*) allocate_and_copy(&(__VA_ARGS__), sizeof(__VA_ARGS__))
#pragma expand NEW
struct X {
const int i;
};
int main() {
struct X * p = (struct X*) allocate_and_copy(&((struct X) {0}), sizeof((struct X) {0}));
} int main() {
struct X * owner p = malloc(sizeof(struct X));
p->text = malloc(10);
free(p->text); //object text destroyed
//p->text is on uninitialized state.
//cannot be used (except assignment)
struct X x2 = {0};
*p = x2; //x2 MOVED TO *p
x_delete(p); struct X {
struct Y * pY;
};
struct Y {
char * owner name;
};
An object Y pointed by pY must live longer than object X.
(Cake is not checking this scenario yet) int * max(int * p1, int * p2) {
return *p1 > *p2 ? p1 : p2;
}
int main(){
int * p = NULL;
int a = 1;
{
int b = 2;
p = max(&a, &b);
}
printf("%d", *p);
}
This is not implemented yet but I want to make the lifetime of p be the smallest scope. (this is to avoid lifetime annotations) int * p = NULL;
int a = 1;
{
int b = 2;
p = max(&a, &b);
} //p cannot be used beyond this point* int * owner p = calloc(1, sizeof(int));
defer free(p);
However, with ownership checks, the code is already safe. This may also change the programmer's style, as generally, C code avoids returns in the middle of the code. struct X {
FILE * owner file;
};
int main(){
struct X x = {};
//....
} //error x.file not freed void f(int n, int a[n]) {
}
"Owner pointers" must be uninitialized or null at the end of their scope.
Basically, the nullable state needs to be tracked at compile time, and nullable pointers,despite being a separate feature, reuse the same flow analysis.
For the impatient reader, a simplified way to think about it is to compare it with C++'s unique_ptr.
The difference is that, instead of runtime code being executed at the end of the scope (a destructor), we perform a compile-time check to ensure that the owner pointer is not referring to any object. The same before assignment.
So we get the same guarantees as C++ RAII, with some extras. In C++, the user has to adopt unique_ptr and additional wrappers (for example, for FILE). In this model, it works directly with malloc, fopen, etc., and is automatically safe, without the user having to opt in to "safety" or write wrappers. Safety is the default, and the safety requirements are propagated automatically.
It is interesting to note that propagation also works very well for struct members. Having an owner pointer as a struct member requires the user to provide a correct "destructor" or free the member manually before the struct object goes out of scope.
#pragma safety enable
#include <stdio.h>
int main() { FILE *_Owner _Opt f = fopen("file.txt", "r"); if (f) { fclose(f); } }
At the end of the scope of f, it can be in one of two possible states: "null" or "moved" (f is moved in the fclose call).
These are the expected states for an owner pointer at the end of its scope, so no warnings are issued.
Removing _Owner _Opt we have exactly the same code as users write today. But with the same or more guarantees than C++ RAII.