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

Generic way to specify a non-null value for optional pointers to zero-bit types? #4537

Closed
ghost opened this issue Feb 23, 2020 · 8 comments
Closed
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library.
Milestone

Comments

@ghost
Copy link

ghost commented Feb 23, 2020

Background: optional pointers to zero-bit types are basically booleans. It's easy to set them to null, but not so easy to set them to "not-null".

I want to do this:

fn function(comptime T: type) void {
    std.debug.assert(@sizeOf(T) == 0);
    var ptr: ?*T = // what goes here?
}

Maybe add a new function in std.meta, which does something like this? -

pub fn notNull(comptime T: type) ?*T {
    // possibly first go over @typeInfo(T) and throw compile errors if the type is
    // type, comptime_int, comptime_float, maybe enum literal...

    // this looks sketchy
    var x: T = undefined;
    return &x;
}

Is there a better way to do this that I'm not aware of?

Examples of zero-bit types:

  • void
  • u0
  • struct {}
  • struct { nothing: u0 }
  • union { nothing: u0 }
  • enum { one }
  • *u0
  • [8]u0
  • [0]u8
  • @Vector(8, u0)
  • @Vector(0, u8)

Some more exotic ones (it's legal to make pointers to these):

  • @TypeOf(null)
  • @TypeOf(undefined)
@SpexGuy
Copy link
Contributor

SpexGuy commented Feb 24, 2020

Is there an actual use case for this? As I understand it, zero-length pointers exist as an optimization of generic code that is compatible with a non-zero-length type. If you know that the type is zero-length, why use an optional pointer instead of a bool?
Since they are an optimization, code that uses zero-length pointers still needs to act like it's taking a pointer to an actual value.
Is the following implementation of your function acceptable?

fn function(comptime T: type) void {
    comptime std.debug.assert(@sizeOf(T) == 0);
    var value: T = undefined;
    var ptr: ?*T = &value;
}

@alexnask
Copy link
Contributor

alexnask commented Feb 26, 2020

The following probably should work:

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

pub fn notNull(comptime T: type) ?*T {
    comptime assert(@sizeOf(T) == 0);
    return @intToPtr(?*T, 0xdeadbeef);
}

test "notNull" {
    var ptr = notNull(u0);
    assert(ptr != null);
}

Although it seems both assert(ptr != null) and assert(ptr == null) pass (returning a null pointer works as expected)

@andrewrk andrewrk added the question No questions on the issue tracker, please. label Feb 29, 2020
@andrewrk andrewrk added this to the 0.7.0 milestone Feb 29, 2020
@andrewrk
Copy link
Member

    var ptr: ?*T = // what goes here?

how about:

    var ptr: ?*T = @as(*T, undefined);

I guess you kind of answered that above. Maybe it would be helpful to understand the real world motivation?

@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library. and removed question No questions on the issue tracker, please. labels Oct 17, 2020
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 17, 2020
@thejoshwolfe
Copy link
Contributor

There's no generic way to specify a non-null ?T at all. Even if T was u32 or *u32, there's no generic way to get a value of the appropriate type.

For each of the 0 bit types you listed, there is a specific way to get a value, then you could take the pointer to that value.

const x = {};
return &x;

const x = @as(u0, 0);
return &x;

I'm having trouble imagining a generic use case for generating a value of T separated from the code that is specifying what T is.

@thejoshwolfe
Copy link
Contributor

the undefined trick doesn't work given the decision about coercing undefined values here: #1831 (comment)

@SpexGuy
Copy link
Contributor

SpexGuy commented Nov 6, 2020

I'm not sure what the official stance is here, but I was under the impression that zero-sized types could not actually take on the value undefined. So @as(T, undefined) returns a defined value when T is zero-sized, and therefore @as(?T, @as(T, undefined)) is not actually coercing undefined to ?T.

@ghost
Copy link
Author

ghost commented Nov 6, 2020

I can't remember my use case but I think I did come by it honestly. I think it was something like: I had a generic function providing a typed API over a u8-array backing. There was some logic that I had that did not work on 0 bytes type so I had have a special case with @sizeOf(T) == 0.

Something like this hackneyed example:

fn Things(comptime T: type) type {
    return struct {
        memory: []const u8,
        num_items: usize,

        fn getFirst(self: *@This()) ?T {
            if (@sizeOf(T) == 0) {
                if (self.num_items > 0) {
                    const nothing: T = undefined;
                    return nothing;
                } else {
                    return null;
                }
            }
            // some logic that doesn't work on 0 byte types 
            const items = std.mem.bytesAsSlice(T, &self.memory);
            if (self.num_items > 0) {
                return items[0];
            } else {
                return null;
            }
        }
    };
}

But there are no pointers in this example, so maybe the problem wasn't about pointers specifically? Or it's possible this recreation has nothing to do with my original use case. Again I can't remember. I should have got around to replying back in February when I was doing this.

@SpexGuy
Copy link
Contributor

SpexGuy commented Nov 21, 2020

At the core of this issue is the question "can a zero-sized value actually contain undefined?". After some discussion with @thejoshwolfe and @andrewrk , we've decided that they cannot. So saying var x: void = undefined; initializes x to the defined value of void. With the decision in #6706 , pointers to zero-sized values are now actual pointers, so a lot of the special cases that this issue addresses go away. But if you still need to construct a pointer without a value, the canonical way is

var x: T = undefined;
return &x;

You could alternatively use @intToPtr(?*T, some_nonzero_sentinel).

@SpexGuy SpexGuy closed this as completed Nov 21, 2020
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. standard library This issue involves writing Zig code for the standard library.
Projects
None yet
Development

No branches or pull requests

4 participants