Hacker News new | ask | show | jobs
by thradams 847 days ago
In cake object and memory are two resources. We can for instance, delete the object and reuse the same memory.

For instance, this code is correct.

    #include <ownership.h> 
    #include <stdlib.h>

    struct X {
       char * owner text;
    };

    void x_delete(struct X * owner p)
    {
        if (p)
        {
           free(p->text);
           free(p);    
        }
    }

    int main() {   
       struct X * owner p = malloc(sizeof(struct X));
        
       p->text = malloc(10);

       free(p->text); //object text destroyed

       struct X x2 = {0};

       *p = x2; //x2 MOVED TO *p

       x_delete(p);   

       //no need to destroy x2
    }
1 comments

    let p: Box<X> = Box::new(X { ... });

    let x2 = X { ... };

    // Moves x2 into the same memory as the first X.
    // The first X is automatically dropped as part of this assignment.
    // Also consumes x2 so x2 is not available any more.
    *p = x2;

    // Drops the X that was originally assigned to x2 and then moved into p.
    drop(p);

    // No need, nor is it possible, to destroy x2.
Thanks for the rust sample. It looks very similar. Can the allocator be customized?

As I said I am not Rust specialist.

Also, in my understanding is that in Rust, sometimes a dynamic state is created when the object may or may not be moved.

In cake ownership this needs to me explicit ( and the destructor is not generated)

I also had a look at Rust in lifetime annotations. This concept may be necessary but I am avoiding it.

Consider this sample.

   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)

Also (classic Rust sample)

    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*
>Can the allocator be customized?

`Box<T>` is the type of an owning pointer that uses the default global allocator, and `Box<T, A>` is the type of an owning pointer that uses an allocator of type `A`. The latter is unstable, ie it can only be used in nightly Rust.

(Also the fact that the latter changes the type means a large part of existing third-party code as well as a bunch of code in libstd itself becomes unusable if you want to use a type-level custom allocator because they only work with `Box<T>`. But that's a different discussion...)

>An object Y pointed by pY must live longer than object X.

Yes, the py field in Rust would use a reference type instead of a pointer, and the reference would need to have a lifetime annotation, and the compiler would work to prevent the situation you describe:

    struct X<'a> { py: &'a Y }

    let y = Y { ... };
    let x = X { py: &y };
    drop(y); // error: y is borrowed by x so it cannot be moved.
But to be clear, the `'a` lifetime syntax is not what's making this work. What's making this work is that the compiler tracks the lifetimes of references in general. This works in the same way even though there are no lifetime annotations:

    let y: String = "a".to_owned();
    let x = &y;
    drop(y); // error: y is borrowed by x so it cannot be moved.
    do_something_with(x);
The explicit lifetime annotations are just for a) readability, and b) because sometimes you want to name them to be able to express relationships between them. Eg if two lifetimes 'a and 'b are in play and you want to express that 'a is at least as long as 'b, then you have to write a `'a: 'b` bound. In many cases they can be omitted and the compiler infers them automatically.
(question about rust.. this is not implemented in cake yet)

Let's say I have to objects on the heap. A and B. A have a "view" to B.

Then we put a prompt for the user. (or dynamic condition) "Which object do you want to delete first A or B?" Then user select B. How this can be checked at compile time?

The code path that drops B will not compile unless that code path drops A first. It doesn't matter if that code path is in response to user input or not. Again, as I said, the point is that the compiler tracks the lifetime of all references. In this case A contains a reference to B, so any code that drops B without dropping A will not compile.
> Also, in my understanding is that in Rust, sometimes a dynamic state is created when the object may or may not be moved.

Yes?

    if cond {
        drop(p)
    }
    // p may or may not be dropped here
or

    let p;
    if cond {
        p = something();
    }
    // p may or may not be set here
These trigger dynamic drop semantics, in which case the stackframe has a hidden set of drop flags going alongside any variable with dynamic drop semantics, to know if they do or don’t need to be dropped. The flags are automatically updated when the corresponding variables are set or moved-from.
This "dynamic drop semantics" does not exist in cake.

    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);
And as bonus there is no temporal hole where you could access a null or dangling text.

Here it is as a runnable snippet: https://godbolt.org/z/fc4Gfxrfd

In cake there is no temporal hole, we cannot reuse the deleted object. This prevents double free and use after free.

    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);