Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Improving Ptrs and Ownership #885

Closed
BraedonWooding opened this issue Apr 3, 2018 · 3 comments
Closed

Proposal: Improving Ptrs and Ownership #885

BraedonWooding opened this issue Apr 3, 2018 · 3 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@BraedonWooding
Copy link
Contributor

BraedonWooding commented Apr 3, 2018

edited to use std.heap :).

Okay currently pointers that aren't 'local' or stack based seem to be unconventional. I think Zig could benefit from a 'smart pointer' like system (that doesn't go overboard of course). I have only coded some basic things in Zig but due to its similarity the code should be correct :).

Std way

const std = @import("std");
const heap = std.heap;
const assert = std.debug.assert;

fn getPtr() error!&int {
    // Normally if it was just 
    // var x = 4; return &x; then it would be a null ptr
    var x = try heap.c_allocator.alloc(int, @sizeof(int));
    *x = 4;
    return x;
}

test "MallocTest" {
    // As this is a test we want to produce error if it runs out of memory
    var ptr : &i32 = try getPtr();
    assert(*ptr == 4);
    heap.c_allocator.free(ptr);
}

Now of course a lot of memory issues come from this being used badly so adding a few ownership tools could help the first being a struct ownership tag.

const Foo = struct {
    bar: &i32;
    @owns(bar); // Or maybe #owns bar: ∫
}

test "Ownership Struct" {
    var ptr : ?&i32 = try getPtr();
    {
         // Cast to non-nullable
         const foo = Foo { .bar = ptr ?? unreachable };
         assert(*foo.bar == 4);
    }
    assert(ptr == null);
}

Furthermore something like the idea of a unique ptr which would just be like this;

pub fn MakeUniquePtr(T: Type, value: T, alignment: u29) UniquePtr {
    var x = heap.c_allocator.alloc(T, @sizeof(T));
    *x = value;
    return struct {
        ref: &T = x;
        @owns(ptr);
        @hidden(ptr); // private
        pub fn get(self: this) T {
            return *ref;
        }
    }
}

test "UniquePtr" {
    var ptr = MakeUniquePtr(int, 4);
    assert(ptr.get() == 4);
}

Which is actually quite nice to be honest. This does prevent copying just so its close to the spec of C++, however I would say that we don't need to prevent it as this is more around just who owns the ptr then anything.

I feel the best way to get involved is jumping in the deep end so hopefully I got most things right :).

@Hejsil
Copy link
Contributor

Hejsil commented Apr 3, 2018

Related: #494, #782 and #453

@andrewrk andrewrk added this to the 0.4.0 milestone Apr 4, 2018
@bheads
Copy link

bheads commented May 7, 2018

Sorry if this is off topic but there are 4 or 5 issues about ownership and this looked like the most recent open.

I was wondering if there wound be a way to make returns from functions as transferring ownership in the same way the errors are returned and the compiler enforces that the caller handles it? Even if it means ignoring or transferring the ownership somewhere else.

// syntax is total rubbish made up but % means returns ownership
pub fn alloc(size: size_t) %&void {  return malloc(size); } // assumes no error or null in this bad example

pub fn bar() %&void {
  return alloc(9); // okay, ownership is returned.. 
}

...

var ptr = alloc(64);  // error ownership not handled...
var ptr = alloc(64) clean defer |p| free(p); // ok you defered the clean up
var ptr = alloc(64) clean defer |p| {};   // ok you defered the clean up but yeah.. totally leaking bub

// This idea can also work for other resources

struct File {
   ... data
   pub fn open(name: []u8) !%File  {
     return File { ... };
   }
   pub fn close(self: &Self) ...
}

var myfile = File.open("passwords.txt") catch |e| { warn("Crap!"); return false; } clean defer |fp| fp.close();

I think the next step would be to transfer ownership as arguments:

pub fn UniquePtr.init(comptime T: type, ptr: %&T) UniquePtr { ... }

var ptr = try UniquePtr.init( MyWidget, alloc(@sizeOf(MyWidget)));  // ok - since UniquePtr is taking ownership of ptr.  

pub fn bad(size: size_t) &void {  return malloc(size); } // is not returning ownership!!!

var ptr = try UniquePtr.init( MyWidget, bad(@sizeOf(MyWidget))); // Error - cannot take ownership

var ptr = try UniquePtr.init( MyWidget, @owns(bad(@sizeOf(MyWidget)))); // okay, casting as owned??

The last part of the puzzle is:
var ptr = alloc(64) clean defer |p| free(p);
what is the type of ptr?

  1. is it just a void pointer?
  2. or is it a void pointer that owns it memory?

If it is case 1 then the compiler stops enforcing ownership rules but in case 2 we can add more safty checks..

var myfile: %File = File.open("file.txt") clean defer {}; // ignore clean up... but myfile owns the File

var tmp = myfile; // transfer ownership of myfile to temp.
assert(myfile == null);

var logger = SomeLogger.init(tmp); // transfer the File ownership to SomeLogger
assert(tmp == null);

Of course these means the ownership would have to be nullable, or if we want to keep nullable separate then myfile would have to be nullable as well.

var myfile: !%?File = try File.open("file.txt") clean defer {}; // ignore clean up... but myfile owns the File

var a: %Foo = ...; /// not nullable
var b = a; // Error A is not nullable!!! 

You should be able to barrow from an owned:

SomeFunction( @barrow(myfile) ); // this would just cast a way the % owns, ie nothing like Rust barrow rules...

One thing I am not sure about at all is if you transfer ownership var tmp = myfile; Should you have to include a clean defer every time?

var a : %?FooThing = alloc(FooThing, @sizeOf(FooThing)) noclean; 
var b = a noclean;

gui.displayFoos(b); // what do you do here?.... 
gui.displayFoos(b); // what do you do here?.... 

This could give us basic compiler enforced ownership without crazy lifetime analysis or forcing everything to use the ownership system.

Sorry for the long rant...

@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Feb 7, 2019
@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Sep 20, 2019
@andrewrk
Copy link
Member

The clean defer stuff looks like #782.

As for the original proposal, it's not really clear to me how it works or what the benefits are, and looking at it, I'm not inspired to do anything with it.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Oct 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

4 participants